From e1acc25a275c58408a3481136526cef92a8d2180 Mon Sep 17 00:00:00 2001 From: Enigbe Date: Thu, 26 Feb 2026 13:41:08 +0100 Subject: [PATCH] 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/event.rs | 5 +-- src/lib.rs | 63 ++++++++++++++++++++++++++++++++- tests/integration_tests_rust.rs | 34 ++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) 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(); +}