Skip to content

Commit 91c483c

Browse files
committed
Expose graph node announcement features
Include the feature map from LDK's NodeAnnouncementInfo in GraphNodeAnnouncement. Features are part of the BOLT 7 node_announcement payload, so exposing them makes the graph node announcement proto match the underlying gossip data more completely. Reuse the existing bit-keyed Feature representation and conversion helper, and cover the new field in the graph-get-node e2e test. This also supports callers such as sim-ln that need to inspect other nodes' advertised capabilities. For context: bitcoin-dev-project/sim-ln#307 https://github.com/bitcoin-dev-project/sim-ln/pull/307/changes#r3481510079
1 parent 8163f4f commit 91c483c

5 files changed

Lines changed: 53 additions & 4 deletions

File tree

e2e-tests/tests/e2e.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,11 +1111,47 @@ async fn test_cli_graph_with_channel() {
11111111
assert!(node_ids.contains(&server_a.node_id()), "Expected server_a in graph nodes");
11121112
assert!(node_ids.contains(&server_b.node_id()), "Expected server_b in graph nodes");
11131113

1114-
// Test GraphGetNode: should return node info with at least one channel.
1115-
let output = run_cli(&server_a, &["graph-get-node", server_b.node_id()]);
1114+
// Test GraphGetNode: should return node info with at least one channel and
1115+
// node announcement features once the node announcement reaches the graph.
1116+
let output = {
1117+
let start = std::time::Instant::now();
1118+
loop {
1119+
let output = run_cli(&server_a, &["graph-get-node", server_b.node_id()]);
1120+
let node = &output["node"];
1121+
let has_channel =
1122+
node["channels"].as_array().is_some_and(|channels| !channels.is_empty());
1123+
let has_announcement_features = node["announcement_info"]["features"]
1124+
.as_object()
1125+
.is_some_and(|features| !features.is_empty());
1126+
1127+
if has_channel && has_announcement_features {
1128+
break output;
1129+
}
1130+
if start.elapsed() > Duration::from_secs(30) {
1131+
panic!("Timed out waiting for node announcement features in network graph");
1132+
}
1133+
tokio::time::sleep(Duration::from_secs(1)).await;
1134+
}
1135+
};
11161136
let node = &output["node"];
11171137
let channels = node["channels"].as_array().unwrap();
11181138
assert!(!channels.is_empty(), "Expected node to have at least one channel");
1139+
1140+
let announcement_info = &node["announcement_info"];
1141+
let features = announcement_info["features"].as_object().unwrap();
1142+
assert!(!features.is_empty(), "Expected node announcement features");
1143+
1144+
// Every entry should be keyed by the signaled bit and expose the decoded name
1145+
// plus whether that bit is required.
1146+
for (bit, feature) in features {
1147+
assert!(bit.parse::<u32>().is_ok(), "Feature key is not a bit number: {bit}");
1148+
assert!(feature.get("name").is_some(), "Feature missing name field");
1149+
assert!(feature.get("is_required").is_some(), "Feature missing is_required field");
1150+
}
1151+
1152+
let keysend = &features["55"];
1153+
assert_eq!(keysend["name"], "Keysend");
1154+
assert_eq!(keysend["is_required"], false);
11191155
}
11201156

11211157
#[tokio::test]

ldk-server-grpc/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fn generate_protos() {
4040
"api.GetNodeInfoResponse.features",
4141
"api.DecodeInvoiceResponse.features",
4242
"api.DecodeOfferResponse.features",
43+
"types.GraphNodeAnnouncement.features",
4344
])
4445
.type_attribute(
4546
".",

ldk-server-grpc/src/proto/types.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,9 @@ message GraphNodeAnnouncement {
798798

799799
// List of addresses on which this node is reachable.
800800
repeated string addresses = 4;
801+
802+
// Features signaled in this node announcement, keyed by feature bit.
803+
map<uint32, Feature> features = 5;
801804
}
802805

803806
// Details of a known Lightning peer.

ldk-server-grpc/src/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,9 @@ pub struct GraphNodeAnnouncement {
10191019
/// List of addresses on which this node is reachable.
10201020
#[prost(string, repeated, tag = "4")]
10211021
pub addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
1022+
/// Features signaled in this node announcement, keyed by feature bit.
1023+
#[prost(btree_map = "uint32, message", tag = "5")]
1024+
pub features: ::prost::alloc::collections::BTreeMap<u32, Feature>,
10221025
}
10231026
/// Details of a known Lightning peer.
10241027
/// See more: <https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_peers>

ldk-server/src/util/proto_adapter.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10-
use bytes::Bytes;
11-
use hex::prelude::*;
1210
use std::collections::BTreeMap;
1311

12+
use bytes::Bytes;
13+
use hex::prelude::*;
1414
use ldk_node::bitcoin::hashes::sha256;
1515
use ldk_node::bitcoin::Network;
1616
use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure};
@@ -19,6 +19,7 @@ use ldk_node::lightning::routing::gossip::{
1919
ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo, RoutingFees,
2020
};
2121
use ldk_node::lightning_invoice::{Bolt11InvoiceDescription, Description, Sha256};
22+
use ldk_node::lightning_types::features::NodeFeatures;
2223
use ldk_node::payment::{
2324
ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus,
2425
};
@@ -472,11 +473,16 @@ pub(crate) fn graph_node_announcement_to_proto(
472473
announcement: NodeAnnouncementInfo,
473474
) -> ldk_server_grpc::types::GraphNodeAnnouncement {
474475
let rgb = announcement.rgb();
476+
let features = features_to_proto(announcement.features().le_flags(), |bytes| {
477+
NodeFeatures::from_le_bytes(bytes).to_string()
478+
});
479+
475480
ldk_server_grpc::types::GraphNodeAnnouncement {
476481
last_update: announcement.last_update(),
477482
alias: announcement.alias().to_string(),
478483
rgb: format!("{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]),
479484
addresses: announcement.addresses().iter().map(|a| a.to_string()).collect(),
485+
features,
480486
}
481487
}
482488

0 commit comments

Comments
 (0)