From 436fb955aa80e0042e593e068efa0929fd9fde53 Mon Sep 17 00:00:00 2001 From: Enigbe Date: Thu, 26 Feb 2026 13:41:08 +0100 Subject: [PATCH 1/3] Expose APIs to access supported feature flags With this, we can read the features supported by the node as announced in node and channel announcements, within an init message, and within a BOLT 11 invoice. We write a small integration test to assert that, at minimum, certain features are supported by default, for example, keysend support. --- src/lib.rs | 63 ++++++++++++++++++++++++++++++++- tests/integration_tests_rust.rs | 34 ++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 109ade0ae..4972cc1f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,8 @@ use lightning::impl_writeable_tlv_based; use lightning::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT; 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; @@ -156,6 +157,9 @@ use lightning_background_processor::process_events_async; pub use lightning_invoice; pub use lightning_liquidity; pub use lightning_types; +use lightning_types::features::{ + Bolt11InvoiceFeatures, ChannelFeatures, InitFeatures, NodeFeatures, +}; use liquidity::{LSPS1Liquidity, LiquiditySource}; use lnurl_auth::LnurlAuth; use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; @@ -1931,6 +1935,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 3fde52dc4..5aa47ebe1 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -2805,3 +2805,37 @@ async fn splice_in_with_all_balance() { node_a.stop().unwrap(); node_b.stop().unwrap(); } + +#[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(); +} From f4cef50b6fc0225199651854f4e715f473227f8b Mon Sep 17 00:00:00 2001 From: Enigbe Date: Tue, 17 Mar 2026 14:52:46 +0100 Subject: [PATCH 2/3] fixup! Expose APIs to access supported feature flags remove per-peer/channel features --- src/lib.rs | 41 ++------------------------------- tests/integration_tests_rust.rs | 34 --------------------------- 2 files changed, 2 insertions(+), 73 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4972cc1f2..0da463afc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,9 +157,7 @@ use lightning_background_processor::process_events_async; pub use lightning_invoice; pub use lightning_liquidity; pub use lightning_types; -use lightning_types::features::{ - Bolt11InvoiceFeatures, ChannelFeatures, InitFeatures, NodeFeatures, -}; +use lightning_types::features::NodeFeatures; use liquidity::{LSPS1Liquidity, LiquiditySource}; use lnurl_auth::LnurlAuth; use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; @@ -1936,7 +1934,7 @@ impl Node { }) } - /// Return the features used in node announcement. + /// Returns the node's invariant 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) => { @@ -1957,41 +1955,6 @@ impl Node { .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 5aa47ebe1..3fde52dc4 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -2805,37 +2805,3 @@ async fn splice_in_with_all_balance() { node_a.stop().unwrap(); node_b.stop().unwrap(); } - -#[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(); -} From 3ac1658fc293d8b2429e75e8b085e70411d51ad9 Mon Sep 17 00:00:00 2001 From: Enigbe Date: Tue, 17 Mar 2026 14:54:57 +0100 Subject: [PATCH 3/3] fixup! Expose APIs to access supported feature flags rename node_features to just features --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0da463afc..ae2e6095d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1935,7 +1935,7 @@ impl Node { } /// Returns the node's invariant features used in node announcement. - pub fn node_features(&self) -> NodeFeatures { + pub fn 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()