Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions e2e-tests/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,11 +1111,47 @@ async fn test_cli_graph_with_channel() {
assert!(node_ids.contains(&server_a.node_id()), "Expected server_a in graph nodes");
assert!(node_ids.contains(&server_b.node_id()), "Expected server_b in graph nodes");

// Test GraphGetNode: should return node info with at least one channel.
let output = run_cli(&server_a, &["graph-get-node", server_b.node_id()]);
// Test GraphGetNode: should return node info with at least one channel and
// node announcement features once the node announcement reaches the graph.
let output = {
let start = std::time::Instant::now();
loop {
let output = run_cli(&server_a, &["graph-get-node", server_b.node_id()]);
let node = &output["node"];
let has_channel =
node["channels"].as_array().is_some_and(|channels| !channels.is_empty());
let has_announcement_features = node["announcement_info"]["features"]
.as_object()
.is_some_and(|features| !features.is_empty());

if has_channel && has_announcement_features {
break output;
}
if start.elapsed() > Duration::from_secs(30) {
panic!("Timed out waiting for node announcement features in network graph");
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
};
let node = &output["node"];
let channels = node["channels"].as_array().unwrap();
assert!(!channels.is_empty(), "Expected node to have at least one channel");

let announcement_info = &node["announcement_info"];
let features = announcement_info["features"].as_object().unwrap();
assert!(!features.is_empty(), "Expected node announcement features");

// Every entry should be keyed by the signaled bit and expose the decoded name
// plus whether that bit is required.
for (bit, feature) in features {
assert!(bit.parse::<u32>().is_ok(), "Feature key is not a bit number: {bit}");
assert!(feature.get("name").is_some(), "Feature missing name field");
assert!(feature.get("is_required").is_some(), "Feature missing is_required field");
}

let keysend = &features["55"];
assert_eq!(keysend["name"], "Keysend");
assert_eq!(keysend["is_required"], false);
}

#[tokio::test]
Expand Down
1 change: 1 addition & 0 deletions ldk-server-grpc/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fn generate_protos() {
"api.GetNodeInfoResponse.features",
"api.DecodeInvoiceResponse.features",
"api.DecodeOfferResponse.features",
"types.GraphNodeAnnouncement.features",
])
.type_attribute(
".",
Expand Down
3 changes: 3 additions & 0 deletions ldk-server-grpc/src/proto/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,9 @@ message GraphNodeAnnouncement {

// List of addresses on which this node is reachable.
repeated string addresses = 4;

// Features signaled in this node announcement, keyed by feature bit.
map<uint32, Feature> features = 5;
}

// Details of a known Lightning peer.
Expand Down
3 changes: 3 additions & 0 deletions ldk-server-grpc/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,9 @@ pub struct GraphNodeAnnouncement {
/// List of addresses on which this node is reachable.
#[prost(string, repeated, tag = "4")]
pub addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
/// Features signaled in this node announcement, keyed by feature bit.
#[prost(btree_map = "uint32, message", tag = "5")]
pub features: ::prost::alloc::collections::BTreeMap<u32, Feature>,
}
/// Details of a known Lightning peer.
/// See more: <https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_peers>
Expand Down
10 changes: 8 additions & 2 deletions ldk-server/src/util/proto_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
// You may not use this file except in accordance with one or both of these
// licenses.

use bytes::Bytes;
use hex::prelude::*;
use std::collections::BTreeMap;

use bytes::Bytes;
use hex::prelude::*;
use ldk_node::bitcoin::hashes::sha256;
use ldk_node::bitcoin::Network;
use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure};
Expand All @@ -19,6 +19,7 @@ use ldk_node::lightning::routing::gossip::{
ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo, RoutingFees,
};
use ldk_node::lightning_invoice::{Bolt11InvoiceDescription, Description, Sha256};
use ldk_node::lightning_types::features::NodeFeatures;
use ldk_node::payment::{
ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus,
};
Expand Down Expand Up @@ -472,11 +473,16 @@ pub(crate) fn graph_node_announcement_to_proto(
announcement: NodeAnnouncementInfo,
) -> ldk_server_grpc::types::GraphNodeAnnouncement {
let rgb = announcement.rgb();
let features = features_to_proto(announcement.features().le_flags(), |bytes| {
NodeFeatures::from_le_bytes(bytes).to_string()
});

ldk_server_grpc::types::GraphNodeAnnouncement {
last_update: announcement.last_update(),
alias: announcement.alias().to_string(),
rgb: format!("{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]),
addresses: announcement.addresses().iter().map(|a| a.to_string()).collect(),
features,
}
}

Expand Down
Loading