diff --git a/src/event.rs b/src/event.rs index a4dcc8cf3..7e1dedf26 100644 --- a/src/event.rs +++ b/src/event.rs @@ -49,8 +49,9 @@ use crate::payment::store::{ PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, }; use crate::runtime::Runtime; -use crate::types::KeysManager; -use crate::types::{CustomTlvRecord, DynStore, OnionMessenger, PaymentStore, Sweeper, Wallet}; +use crate::types::{ + CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet, +}; use crate::{ hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore, UserChannelId, diff --git a/src/lib.rs b/src/lib.rs index 1b93cb6e9..ceba8dfaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,12 +141,16 @@ use lightning::chain::BestBlock; use lightning::impl_writeable_tlv_based; use lightning::ln::channel_state::{ChannelDetails as LdkChannelDetails, ChannelShutdownState}; use lightning::ln::channelmanager::PaymentId; -use lightning::ln::msgs::SocketAddress; +use lightning::ln::msgs::{BaseMessageHandler, SocketAddress}; +use lightning::ln::peer_handler::CustomMessageHandler; use lightning::routing::gossip::NodeAlias; use lightning::sign::EntropySource; use lightning::util::persist::KVStoreSync; use lightning::util::wallet_utils::Wallet as LdkWallet; use lightning_background_processor::process_events_async; +use lightning_types::features::{ + Bolt11InvoiceFeatures, ChannelFeatures, InitFeatures, NodeFeatures, +}; use liquidity::{LSPS1Liquidity, LiquiditySource}; use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; use payment::asynchronous::om_mailbox::OnionMessageMailbox; @@ -1725,6 +1729,63 @@ impl Node { Error::PersistenceFailed }) } + + /// Return the features used in node announcement. + pub fn node_features(&self) -> NodeFeatures { + let gossip_features = match self.gossip_source.as_gossip_sync() { + lightning_background_processor::GossipSync::P2P(p2p_gossip_sync) => { + p2p_gossip_sync.provided_node_features() + }, + lightning_background_processor::GossipSync::Rapid(_) => NodeFeatures::empty(), + lightning_background_processor::GossipSync::None => { + unreachable!("We must always have a gossip sync!") + }, + }; + self.channel_manager.node_features() + | self.chain_monitor.provided_node_features() + | self.onion_messenger.provided_node_features() + | gossip_features + | self + .liquidity_source + .as_ref() + .map(|ls| ls.liquidity_manager().provided_node_features()) + .unwrap_or_else(NodeFeatures::empty) + } + + /// Return the node's init features. + pub fn init_features(&self) -> InitFeatures { + let gossip_init_features = match self.gossip_source.as_gossip_sync() { + lightning_background_processor::GossipSync::P2P(p2p_gossip_sync) => { + p2p_gossip_sync.provided_init_features(self.node_id()) + }, + lightning_background_processor::GossipSync::Rapid(_) => InitFeatures::empty(), + lightning_background_processor::GossipSync::None => { + unreachable!("We must always have a gossip sync!") + }, + }; + self.channel_manager.init_features() + | self.chain_monitor.provided_init_features(self.node_id()) + | self.onion_messenger.provided_init_features(self.node_id()) + | gossip_init_features + | self + .liquidity_source + .as_ref() + .map(|ls| ls.liquidity_manager().provided_init_features(self.node_id())) + .unwrap_or_else(InitFeatures::empty) + } + + /// Return the node's channel features. + pub fn channel_features(&self) -> ChannelFeatures { + self.channel_manager.channel_features() + } + + /// Return the node's BOLT 11 invoice features. + pub fn bolt11_invoice_features(&self) -> Bolt11InvoiceFeatures { + // bolt11_invoice_features() is not public because feature + // flags can vary due to invoice type, so we convert from + // context. + self.channel_manager.init_features().to_context() + } } impl Drop for Node { diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 61c9c8281..a09080d0b 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -2627,3 +2627,37 @@ async fn onchain_fee_bump_rbf() { assert_eq!(node_a_received_payment[0].amount_msat, Some(amount_to_send_sats * 1000)); assert_eq!(node_a_received_payment[0].status, PaymentStatus::Succeeded); } + +#[test] +fn node_feature_flags() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); + let config = random_config(true); + let node = setup_node(&chain_source, config); + + // NodeFeatures + let node_features = node.node_features(); + assert!(node_features.supports_variable_length_onion()); + assert!(node_features.supports_payment_secret()); + assert!(node_features.supports_basic_mpp()); + assert!(node_features.supports_keysend()); + assert!(node_features.supports_onion_messages()); + + // InitFeatures + let init_features = node.init_features(); + assert!(init_features.supports_variable_length_onion()); + assert!(init_features.supports_payment_secret()); + assert!(init_features.supports_basic_mpp()); + assert!(init_features.supports_onion_messages()); + + // ChannelFeatures (non-empty) + let _channel_features = node.channel_features(); + + // Bolt11InvoiceFeatures + let bolt11_features = node.bolt11_invoice_features(); + assert!(bolt11_features.supports_variable_length_onion()); + assert!(bolt11_features.supports_payment_secret()); + assert!(bolt11_features.supports_basic_mpp()); + + node.stop().unwrap(); +}