diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index a3023eca1..ec63adbe0 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -84,6 +84,7 @@ interface Node { sequence list_payments(); sequence list_peers(); sequence list_channels(); + NetworkGraph network_graph(); [Throws=NodeError] string sign_message([ByRef]sequence msg); boolean verify_signature([ByRef]sequence msg, [ByRef]string sig, [ByRef]PublicKey pkey); @@ -167,6 +168,7 @@ enum NodeError { "InvalidPublicKey", "InvalidSecretKey", "InvalidOfferId", + "InvalidNodeId", "InvalidPaymentId", "InvalidPaymentHash", "InvalidPaymentPreimage", @@ -387,6 +389,46 @@ enum LogLevel { "Error", }; +interface NetworkGraph { + sequence list_channels(); + ChannelInfo? channel(u64 short_channel_id); + sequence list_nodes(); + NodeInfo? node([ByRef]NodeId node_id); +}; + +dictionary ChannelInfo { + NodeId node_one; + ChannelUpdateInfo? one_to_two; + NodeId node_two; + ChannelUpdateInfo? two_to_one; + u64? capacity_sats; +}; + +dictionary ChannelUpdateInfo { + u32 last_update; + boolean enabled; + u16 cltv_expiry_delta; + u64 htlc_minimum_msat; + u64 htlc_maximum_msat; + RoutingFees fees; +}; + +dictionary RoutingFees { + u32 base_msat; + u32 proportional_millionths; +}; + +dictionary NodeInfo { + sequence channels; + NodeAnnouncementInfo? announcement_info; +}; + +dictionary NodeAnnouncementInfo { + u32 last_update; + string alias; + sequence addresses; +}; + [Custom] typedef string Txid; @@ -399,6 +441,9 @@ typedef string SocketAddress; [Custom] typedef string PublicKey; +[Custom] +typedef string NodeId; + [Custom] typedef string Address; diff --git a/src/builder.rs b/src/builder.rs index 2a361396d..5d6243efb 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,7 +15,7 @@ use crate::payment::store::PaymentStore; use crate::peer_store::PeerStore; use crate::tx_broadcaster::TransactionBroadcaster; use crate::types::{ - ChainMonitor, ChannelManager, DynStore, GossipSync, KeysManager, MessageRouter, NetworkGraph, + ChainMonitor, ChannelManager, DynStore, GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PeerManager, }; use crate::wallet::Wallet; @@ -633,7 +633,7 @@ fn build_with_store_internal( Ok(graph) => Arc::new(graph), Err(e) => { if e.kind() == std::io::ErrorKind::NotFound { - Arc::new(NetworkGraph::new(config.network.into(), Arc::clone(&logger))) + Arc::new(Graph::new(config.network.into(), Arc::clone(&logger))) } else { return Err(BuildError::ReadFailed); } diff --git a/src/error.rs b/src/error.rs index 824bde192..63ec0ad84 100644 --- a/src/error.rs +++ b/src/error.rs @@ -55,6 +55,8 @@ pub enum Error { InvalidSecretKey, /// The given offer id is invalid. InvalidOfferId, + /// The given node id is invalid. + InvalidNodeId, /// The given payment id is invalid. InvalidPaymentId, /// The given payment hash is invalid. @@ -120,6 +122,7 @@ impl fmt::Display for Error { Self::InvalidPublicKey => write!(f, "The given public key is invalid."), Self::InvalidSecretKey => write!(f, "The given secret key is invalid."), Self::InvalidOfferId => write!(f, "The given offer id is invalid."), + Self::InvalidNodeId => write!(f, "The given node id is invalid."), Self::InvalidPaymentId => write!(f, "The given payment id is invalid."), Self::InvalidPaymentHash => write!(f, "The given payment hash is invalid."), Self::InvalidPaymentPreimage => write!(f, "The given payment preimage is invalid."), diff --git a/src/event.rs b/src/event.rs index 36769c0ee..5cd9e2603 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,7 +1,7 @@ use crate::types::{DynStore, Sweeper, Wallet}; use crate::{ - hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, NetworkGraph, PeerInfo, + hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, Graph, PeerInfo, PeerStore, UserChannelId, }; @@ -323,7 +323,7 @@ where channel_manager: Arc, connection_manager: Arc>, output_sweeper: Arc, - network_graph: Arc, + network_graph: Arc, payment_store: Arc>, peer_store: Arc>, runtime: Arc>>, @@ -339,7 +339,7 @@ where event_queue: Arc>, wallet: Arc, bump_tx_event_handler: Arc, channel_manager: Arc, connection_manager: Arc>, - output_sweeper: Arc, network_graph: Arc, + output_sweeper: Arc, network_graph: Arc, payment_store: Arc>, peer_store: Arc>, runtime: Arc>>, logger: L, config: Arc, ) -> Self { diff --git a/src/gossip.rs b/src/gossip.rs index de98d441e..5a41bf51c 100644 --- a/src/gossip.rs +++ b/src/gossip.rs @@ -1,5 +1,5 @@ use crate::logger::{log_trace, FilesystemLogger, Logger}; -use crate::types::{GossipSync, NetworkGraph, P2PGossipSync, RapidGossipSync}; +use crate::types::{GossipSync, Graph, P2PGossipSync, RapidGossipSync}; use crate::Error; use lightning::routing::utxo::UtxoLookup; @@ -20,7 +20,7 @@ pub(crate) enum GossipSource { } impl GossipSource { - pub fn new_p2p(network_graph: Arc, logger: Arc) -> Self { + pub fn new_p2p(network_graph: Arc, logger: Arc) -> Self { let gossip_sync = Arc::new(P2PGossipSync::new( network_graph, None::>, @@ -30,7 +30,7 @@ impl GossipSource { } pub fn new_rgs( - server_url: String, latest_sync_timestamp: u32, network_graph: Arc, + server_url: String, latest_sync_timestamp: u32, network_graph: Arc, logger: Arc, ) -> Self { let gossip_sync = Arc::new(RapidGossipSync::new(network_graph, Arc::clone(&logger))); diff --git a/src/graph.rs b/src/graph.rs new file mode 100644 index 000000000..79a21853d --- /dev/null +++ b/src/graph.rs @@ -0,0 +1,166 @@ +//! Objects for querying the network graph. + +use crate::types::Graph; + +use lightning::routing::gossip::NodeId; + +#[cfg(feature = "uniffi")] +use lightning::ln::msgs::SocketAddress; +#[cfg(feature = "uniffi")] +use lightning::routing::gossip::RoutingFees; + +#[cfg(not(feature = "uniffi"))] +use lightning::routing::gossip::{ChannelInfo, NodeInfo}; + +use std::sync::Arc; + +/// Represents the network as nodes and channels between them. +pub struct NetworkGraph { + inner: Arc, +} + +impl NetworkGraph { + pub(crate) fn new(inner: Arc) -> Self { + Self { inner } + } + + /// Returns the list of channels in the graph + pub fn list_channels(&self) -> Vec { + self.inner.read_only().channels().unordered_keys().map(|c| *c).collect() + } + + /// Returns information on a channel with the given id. + pub fn channel(&self, short_channel_id: u64) -> Option { + self.inner.read_only().channels().get(&short_channel_id).cloned().map(|c| c.into()) + } + + /// Returns the list of nodes in the graph + pub fn list_nodes(&self) -> Vec { + self.inner.read_only().nodes().unordered_keys().map(|n| *n).collect() + } + + /// Returns information on a node with the given id. + pub fn node(&self, node_id: &NodeId) -> Option { + self.inner.read_only().nodes().get(node_id).cloned().map(|n| n.into()) + } +} + +/// Details about a channel (both directions). +/// +/// Received within a channel announcement. +/// +/// This is a simplified version of LDK's `ChannelInfo` for bindings. +#[cfg(feature = "uniffi")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ChannelInfo { + /// Source node of the first direction of a channel + pub node_one: NodeId, + /// Details about the first direction of a channel + pub one_to_two: Option, + /// Source node of the second direction of a channel + pub node_two: NodeId, + /// Details about the second direction of a channel + pub two_to_one: Option, + /// The channel capacity as seen on-chain, if chain lookup is available. + pub capacity_sats: Option, +} + +#[cfg(feature = "uniffi")] +impl From for ChannelInfo { + fn from(value: lightning::routing::gossip::ChannelInfo) -> Self { + Self { + node_one: value.node_one, + one_to_two: value.one_to_two.map(|u| u.into()), + node_two: value.node_two, + two_to_one: value.two_to_one.map(|u| u.into()), + capacity_sats: value.capacity_sats, + } + } +} + +/// Details about one direction of a channel as received within a `ChannelUpdate`. +/// +/// This is a simplified version of LDK's `ChannelUpdateInfo` for bindings. +#[cfg(feature = "uniffi")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ChannelUpdateInfo { + /// When the last update to the channel direction was issued. + /// Value is opaque, as set in the announcement. + pub last_update: u32, + /// Whether the channel can be currently used for payments (in this one direction). + pub enabled: bool, + /// The difference in CLTV values that you must have when routing through this channel. + pub cltv_expiry_delta: u16, + /// The minimum value, which must be relayed to the next hop via the channel + pub htlc_minimum_msat: u64, + /// The maximum value which may be relayed to the next hop via the channel. + pub htlc_maximum_msat: u64, + /// Fees charged when the channel is used for routing + pub fees: RoutingFees, +} + +#[cfg(feature = "uniffi")] +impl From for ChannelUpdateInfo { + fn from(value: lightning::routing::gossip::ChannelUpdateInfo) -> Self { + Self { + last_update: value.last_update, + enabled: value.enabled, + cltv_expiry_delta: value.cltv_expiry_delta, + htlc_minimum_msat: value.htlc_minimum_msat, + htlc_maximum_msat: value.htlc_maximum_msat, + fees: value.fees, + } + } +} + +/// Details about a node in the network, known from the network announcement. +/// +/// This is a simplified version of LDK's `NodeInfo` for bindings. +#[cfg(feature = "uniffi")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeInfo { + /// All valid channels a node has announced + pub channels: Vec, + /// More information about a node from node_announcement. + /// Optional because we store a Node entry after learning about it from + /// a channel announcement, but before receiving a node announcement. + pub announcement_info: Option, +} + +#[cfg(feature = "uniffi")] +impl From for NodeInfo { + fn from(value: lightning::routing::gossip::NodeInfo) -> Self { + Self { + channels: value.channels, + announcement_info: value.announcement_info.map(|a| a.into()), + } + } +} + +/// Information received in the latest node_announcement from this node. +/// +/// This is a simplified version of LDK's `NodeAnnouncementInfo` for bindings. +#[cfg(feature = "uniffi")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeAnnouncementInfo { + /// When the last known update to the node state was issued. + /// Value is opaque, as set in the announcement. + pub last_update: u32, + /// Moniker assigned to the node. + /// May be invalid or malicious (eg control chars), + /// should not be exposed to the user. + pub alias: String, + /// List of addresses on which this node is reachable + pub addresses: Vec, +} + +#[cfg(feature = "uniffi")] +impl From for NodeAnnouncementInfo { + fn from(value: lightning::routing::gossip::NodeAnnouncementInfo) -> Self { + Self { + last_update: value.last_update, + alias: value.alias.to_string(), + addresses: value.addresses().iter().cloned().collect(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9c3c12342..b748588a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ mod error; mod event; mod fee_estimator; mod gossip; +pub mod graph; mod hex_utils; pub mod io; mod liquidity; @@ -128,13 +129,14 @@ use config::{ use connection::ConnectionManager; use event::{EventHandler, EventQueue}; use gossip::GossipSource; +use graph::NetworkGraph; use liquidity::LiquiditySource; use payment::store::PaymentStore; use payment::{Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment}; use peer_store::{PeerInfo, PeerStore}; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, FeeEstimator, - KeysManager, NetworkGraph, PeerManager, Router, Scorer, Sweeper, Wallet, + Graph, KeysManager, PeerManager, Router, Scorer, Sweeper, Wallet, }; pub use types::{ChannelDetails, PeerDetails, UserChannelId}; @@ -183,7 +185,7 @@ pub struct Node { peer_manager: Arc, connection_manager: Arc>>, keys_manager: Arc, - network_graph: Arc, + network_graph: Arc, gossip_source: Arc, liquidity_source: Option>>>, kv_store: Arc, @@ -1407,6 +1409,18 @@ impl Node { peers } + /// Returns a handler allowing to query the network graph. + #[cfg(not(feature = "uniffi"))] + pub fn network_graph(&self) -> NetworkGraph { + NetworkGraph::new(Arc::clone(&self.network_graph)) + } + + /// Returns a handler allowing to query the network graph. + #[cfg(feature = "uniffi")] + pub fn network_graph(&self) -> Arc { + Arc::new(NetworkGraph::new(Arc::clone(&self.network_graph))) + } + /// Creates a digital ECDSA signature of a message with the node's secret key. /// /// A receiver knowing the corresponding `PublicKey` (e.g. the node’s id) and the message diff --git a/src/types.rs b/src/types.rs index b7cf4ad44..89a14e163 100644 --- a/src/types.rs +++ b/src/types.rs @@ -80,31 +80,28 @@ pub(crate) type KeysManager = crate::wallet::WalletKeysManager< >; pub(crate) type Router = DefaultRouter< - Arc, + Arc, Arc, Arc, Arc>, ProbabilisticScoringFeeParameters, Scorer, >; -pub(crate) type Scorer = ProbabilisticScorer, Arc>; +pub(crate) type Scorer = ProbabilisticScorer, Arc>; -pub(crate) type NetworkGraph = gossip::NetworkGraph>; +pub(crate) type Graph = gossip::NetworkGraph>; pub(crate) type UtxoLookup = dyn lightning::routing::utxo::UtxoLookup + Send + Sync; -pub(crate) type P2PGossipSync = lightning::routing::gossip::P2PGossipSync< - Arc, - Arc, - Arc, ->; +pub(crate) type P2PGossipSync = + lightning::routing::gossip::P2PGossipSync, Arc, Arc>; pub(crate) type RapidGossipSync = - lightning_rapid_gossip_sync::RapidGossipSync, Arc>; + lightning_rapid_gossip_sync::RapidGossipSync, Arc>; pub(crate) type GossipSync = lightning_background_processor::GossipSync< Arc, Arc, - Arc, + Arc, Arc, Arc, >; @@ -120,7 +117,7 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse >; pub(crate) type MessageRouter = lightning::onion_message::messenger::DefaultMessageRouter< - Arc, + Arc, Arc, Arc, >; diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 99e72e31c..9dd7e5699 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -1,3 +1,4 @@ +pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus}; pub use lightning::events::{ClosureReason, PaymentFailureReason}; @@ -5,6 +6,7 @@ pub use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning::offers::invoice::Bolt12Invoice; pub use lightning::offers::offer::{Offer, OfferId}; pub use lightning::offers::refund::Refund; +pub use lightning::routing::gossip::{NodeId, RoutingFees}; pub use lightning::util::string::UntrustedString; pub use lightning_invoice::Bolt11Invoice; @@ -45,6 +47,22 @@ impl UniffiCustomTypeConverter for PublicKey { } } +impl UniffiCustomTypeConverter for NodeId { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Ok(key) = NodeId::from_str(&val) { + return Ok(key); + } + + Err(Error::InvalidNodeId.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + impl UniffiCustomTypeConverter for Address { type Builtin = String;