From 65714701441b0dbcdc322c0e05f55d5070d1df2c Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 21 Jan 2026 22:58:23 -0600 Subject: [PATCH 1/4] Add SpontaneousSend (keysend) to client and CLI --- ldk-server-cli/src/main.rs | 54 +++++++++++++++++++++++++- ldk-server-client/src/client.rs | 14 ++++++- ldk-server-protos/src/api.rs | 28 +++++++++++++ ldk-server-protos/src/endpoints.rs | 1 + ldk-server-protos/src/proto/api.proto | 22 ++++++++++- ldk-server/src/api/bolt11_send.rs | 31 +-------------- ldk-server/src/api/bolt12_send.rs | 31 +-------------- ldk-server/src/api/mod.rs | 34 ++++++++++++++++ ldk-server/src/api/spontaneous_send.rs | 34 ++++++++++++++++ ldk-server/src/service.rs | 10 ++++- 10 files changed, 196 insertions(+), 63 deletions(-) create mode 100644 ldk-server/src/api/spontaneous_send.rs diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 4e571b3d..9c7418ec 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -29,7 +29,8 @@ use ldk_server_client::ldk_server_protos::api::{ ListChannelsRequest, ListChannelsResponse, ListForwardedPaymentsRequest, ListPaymentsRequest, OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, - SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, + SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, UpdateChannelConfigRequest, + UpdateChannelConfigResponse, }; use ldk_server_client::ldk_server_protos::types::{ bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, @@ -196,6 +197,30 @@ enum Commands { )] max_channel_saturation_power_of_half: Option, }, + #[command(about = "Send a spontaneous payment (keysend) to a node")] + SpontaneousSend { + #[arg(short, long, help = "The hex-encoded public key of the node to send the payment to")] + node_id: String, + #[arg(short, long, help = "The amount in millisatoshis to send")] + amount_msat: u64, + #[arg( + long, + help = "Maximum total fees in millisatoshis that may accrue during route finding. Defaults to 1% of payment + 50 sats" + )] + max_total_routing_fee_msat: Option, + #[arg(long, help = "Maximum total CLTV delta we accept for the route (default: 1008)")] + max_total_cltv_expiry_delta: Option, + #[arg( + long, + help = "Maximum number of paths that may be used by MPP payments (default: 10)" + )] + max_path_count: Option, + #[arg( + long, + help = "Maximum share of a channel's total capacity to send over a channel, as a power of 1/2 (default: 2)" + )] + max_channel_saturation_power_of_half: Option, + }, #[command(about = "Cooperatively close the channel specified by the given channel ID")] CloseChannel { #[arg(short, long, help = "The local user_channel_id of this channel")] @@ -543,6 +568,33 @@ async fn main() { .await, ); }, + Commands::SpontaneousSend { + node_id, + amount_msat, + max_total_routing_fee_msat, + max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + } => { + let route_parameters = RouteParametersConfig { + max_total_routing_fee_msat, + max_total_cltv_expiry_delta: max_total_cltv_expiry_delta + .unwrap_or(DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA), + max_path_count: max_path_count.unwrap_or(DEFAULT_MAX_PATH_COUNT), + max_channel_saturation_power_of_half: max_channel_saturation_power_of_half + .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), + }; + + handle_response_result::<_, SpontaneousSendResponse>( + client + .spontaneous_send(SpontaneousSendRequest { + amount_msat, + node_id, + route_parameters: Some(route_parameters), + }) + .await, + ); + }, Commands::CloseChannel { user_channel_id, counterparty_node_id } => { handle_response_result::<_, CloseChannelResponse>( client diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index 0d137db1..e7630708 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -21,14 +21,15 @@ use ldk_server_protos::api::{ ListForwardedPaymentsResponse, ListPaymentsRequest, ListPaymentsResponse, OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, - SpliceOutResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, + SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, UpdateChannelConfigRequest, + UpdateChannelConfigResponse, }; use ldk_server_protos::endpoints::{ BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SPLICE_IN_PATH, - SPLICE_OUT_PATH, UPDATE_CHANNEL_CONFIG_PATH, + SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, }; use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use prost::Message; @@ -261,6 +262,15 @@ impl LdkServerClient { self.post_request(&request, &url).await } + /// Send a spontaneous payment (keysend) to a node. + /// For API contract/usage, refer to docs for [`SpontaneousSendRequest`] and [`SpontaneousSendResponse`]. + pub async fn spontaneous_send( + &self, request: SpontaneousSendRequest, + ) -> Result { + let url = format!("https://{}/{SPONTANEOUS_SEND_PATH}", self.base_url); + self.post_request(&request, &url).await + } + async fn post_request( &self, request: &Rq, url: &str, ) -> Result { diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index 1f1bbbea..a4ad7f6f 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -258,6 +258,34 @@ pub struct Bolt12SendResponse { #[prost(string, tag = "1")] pub payment_id: ::prost::alloc::string::String, } +/// Send a spontaneous payment, also known as "keysend", to a node. +/// See more: +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SpontaneousSendRequest { + /// The amount in millisatoshis to send. + #[prost(uint64, tag = "1")] + pub amount_msat: u64, + /// The hex-encoded public key of the node to send the payment to. + #[prost(string, tag = "2")] + pub node_id: ::prost::alloc::string::String, + /// Configuration options for payment routing and pathfinding. + #[prost(message, optional, tag = "3")] + pub route_parameters: ::core::option::Option, +} +/// The response `content` for the `SpontaneousSend` API, when HttpStatusCode is OK (200). +/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SpontaneousSendResponse { + /// An identifier used to uniquely identify a payment in hex-encoded form. + #[prost(string, tag = "1")] + pub payment_id: ::prost::alloc::string::String, +} /// Creates a new outbound channel to the given remote node. /// See more: #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/ldk-server-protos/src/endpoints.rs b/ldk-server-protos/src/endpoints.rs index 606da0db..bf9b6949 100644 --- a/ldk-server-protos/src/endpoints.rs +++ b/ldk-server-protos/src/endpoints.rs @@ -26,3 +26,4 @@ pub const LIST_FORWARDED_PAYMENTS_PATH: &str = "ListForwardedPayments"; pub const UPDATE_CHANNEL_CONFIG_PATH: &str = "UpdateChannelConfig"; pub const GET_PAYMENT_DETAILS_PATH: &str = "GetPaymentDetails"; pub const CONNECT_PEER_PATH: &str = "ConnectPeer"; +pub const SPONTANEOUS_SEND_PATH: &str = "SpontaneousSend"; diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index be29f8f8..f1a191c1 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -138,7 +138,7 @@ message Bolt11SendRequest { // amount paid to be determined by the user. // This operation will fail if the amount specified is less than the value required by the given invoice. optional uint64 amount_msat = 2; - + // Configuration options for payment routing and pathfinding. optional types.RouteParametersConfig route_parameters = 3; @@ -215,6 +215,26 @@ message Bolt12SendResponse { string payment_id = 1; } +// Send a spontaneous payment, also known as "keysend", to a node. +// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.SpontaneousPayment.html#method.send +message SpontaneousSendRequest { + // The amount in millisatoshis to send. + uint64 amount_msat = 1; + + // The hex-encoded public key of the node to send the payment to. + string node_id = 2; + + // Configuration options for payment routing and pathfinding. + optional types.RouteParametersConfig route_parameters = 3; +} + +// The response `content` for the `SpontaneousSend` API, when HttpStatusCode is OK (200). +// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +message SpontaneousSendResponse { + // An identifier used to uniquely identify a payment in hex-encoded form. + string payment_id = 1; +} + // Creates a new outbound channel to the given remote node. // See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.connect_open_channel message OpenChannelRequest { diff --git a/ldk-server/src/api/bolt11_send.rs b/ldk-server/src/api/bolt11_send.rs index 917e3230..1298896e 100644 --- a/ldk-server/src/api/bolt11_send.rs +++ b/ldk-server/src/api/bolt11_send.rs @@ -9,12 +9,11 @@ use std::str::FromStr; -use ldk_node::lightning::routing::router::RouteParametersConfig; use ldk_node::lightning_invoice::Bolt11Invoice; use ldk_server_protos::api::{Bolt11SendRequest, Bolt11SendResponse}; +use crate::api::build_route_parameters_config_from_proto; use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; pub(crate) fn handle_bolt11_send_request( @@ -23,33 +22,7 @@ pub(crate) fn handle_bolt11_send_request( let invoice = Bolt11Invoice::from_str(request.invoice.as_str()) .map_err(|_| ldk_node::NodeError::InvalidInvoice)?; - let route_parameters = match request.route_parameters { - Some(params) => { - let max_path_count: u8 = params.max_path_count.try_into().map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid max_path_count, must be between 0 and {}", u8::MAX), - ) - })?; - let max_channel_saturation_power_of_half: u8 = - params.max_channel_saturation_power_of_half.try_into().map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!( - "Invalid max_channel_saturation_power_of_half, must be between 0 and {}", - u8::MAX - ), - ) - })?; - Some(RouteParametersConfig { - max_total_routing_fee_msat: params.max_total_routing_fee_msat, - max_total_cltv_expiry_delta: params.max_total_cltv_expiry_delta, - max_path_count, - max_channel_saturation_power_of_half, - }) - }, - None => None, - }; + let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; let payment_id = match request.amount_msat { None => context.node.bolt11_payment().send(&invoice, route_parameters), diff --git a/ldk-server/src/api/bolt12_send.rs b/ldk-server/src/api/bolt12_send.rs index 34535a04..30df2dd8 100644 --- a/ldk-server/src/api/bolt12_send.rs +++ b/ldk-server/src/api/bolt12_send.rs @@ -10,11 +10,10 @@ use std::str::FromStr; use ldk_node::lightning::offers::offer::Offer; -use ldk_node::lightning::routing::router::RouteParametersConfig; use ldk_server_protos::api::{Bolt12SendRequest, Bolt12SendResponse}; +use crate::api::build_route_parameters_config_from_proto; use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; pub(crate) fn handle_bolt12_send_request( @@ -23,33 +22,7 @@ pub(crate) fn handle_bolt12_send_request( let offer = Offer::from_str(request.offer.as_str()).map_err(|_| ldk_node::NodeError::InvalidOffer)?; - let route_parameters = match request.route_parameters { - Some(params) => { - let max_path_count = params.max_path_count.try_into().map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid max_path_count, must be between 0 and {}", u8::MAX), - ) - })?; - let max_channel_saturation_power_of_half = - params.max_channel_saturation_power_of_half.try_into().map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!( - "Invalid max_channel_saturation_power_of_half, must be between 0 and {}", - u8::MAX - ), - ) - })?; - Some(RouteParametersConfig { - max_total_routing_fee_msat: params.max_total_routing_fee_msat, - max_total_cltv_expiry_delta: params.max_total_cltv_expiry_delta, - max_path_count, - max_channel_saturation_power_of_half, - }) - }, - None => None, - }; + let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; let payment_id = match request.amount_msat { None => context.node.bolt12_payment().send( diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index 1152f8b3..5567a449 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -8,6 +8,7 @@ // licenses. use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; +use ldk_node::lightning::routing::router::RouteParametersConfig; use ldk_server_protos::types::channel_config::MaxDustHtlcExposure; use crate::api::error::LdkServerError; @@ -30,6 +31,7 @@ pub(crate) mod onchain_receive; pub(crate) mod onchain_send; pub(crate) mod open_channel; pub(crate) mod splice_channel; +pub(crate) mod spontaneous_send; pub(crate) mod update_channel_config; pub(crate) fn build_channel_config_from_proto( @@ -75,3 +77,35 @@ pub(crate) fn build_channel_config_from_proto( .unwrap_or(default_config.accept_underpaying_htlcs), }) } + +pub(crate) fn build_route_parameters_config_from_proto( + proto_route_params: Option, +) -> Result, LdkServerError> { + match proto_route_params { + Some(params) => { + let max_path_count = params.max_path_count.try_into().map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid max_path_count, must be between 0 and {}", u8::MAX), + ) + })?; + let max_channel_saturation_power_of_half = + params.max_channel_saturation_power_of_half.try_into().map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!( + "Invalid max_channel_saturation_power_of_half, must be between 0 and {}", + u8::MAX + ), + ) + })?; + Ok(Some(RouteParametersConfig { + max_total_routing_fee_msat: params.max_total_routing_fee_msat, + max_total_cltv_expiry_delta: params.max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + })) + }, + None => Ok(None), + } +} diff --git a/ldk-server/src/api/spontaneous_send.rs b/ldk-server/src/api/spontaneous_send.rs new file mode 100644 index 00000000..77fe3b8a --- /dev/null +++ b/ldk-server/src/api/spontaneous_send.rs @@ -0,0 +1,34 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use std::str::FromStr; + +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_server_protos::api::{SpontaneousSendRequest, SpontaneousSendResponse}; + +use crate::api::build_route_parameters_config_from_proto; +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use crate::service::Context; + +pub(crate) fn handle_spontaneous_send_request( + context: Context, request: SpontaneousSendRequest, +) -> Result { + let node_id = PublicKey::from_str(&request.node_id).map_err(|_| { + LdkServerError::new(InvalidRequestError, "Invalid node_id provided.".to_string()) + })?; + + let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; + + let payment_id = + context.node.spontaneous_payment().send(request.amount_msat, node_id, route_parameters)?; + + let response = SpontaneousSendResponse { payment_id: payment_id.to_string() }; + Ok(response) +} diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index 0f050945..ce3aa833 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -23,7 +23,7 @@ use ldk_server_protos::endpoints::{ CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SPLICE_IN_PATH, - SPLICE_OUT_PATH, UPDATE_CHANNEL_CONFIG_PATH, + SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, }; use prost::Message; @@ -45,6 +45,7 @@ use crate::api::onchain_receive::handle_onchain_receive_request; use crate::api::onchain_send::handle_onchain_send_request; use crate::api::open_channel::handle_open_channel; use crate::api::splice_channel::{handle_splice_in_request, handle_splice_out_request}; +use crate::api::spontaneous_send::handle_spontaneous_send_request; use crate::api::update_channel_config::handle_update_channel_config_request; use crate::io::persist::paginated_kv_store::PaginatedKVStore; use crate::util::proto_adapter::to_error_response; @@ -296,6 +297,13 @@ impl Service> for NodeService { CONNECT_PEER_PATH => { Box::pin(handle_request(context, req, auth_params, api_key, handle_connect_peer)) }, + SPONTANEOUS_SEND_PATH => Box::pin(handle_request( + context, + req, + auth_params, + api_key, + handle_spontaneous_send_request, + )), path => { let error = format!("Unknown request: {}", path).into_bytes(); Box::pin(async { From ca155444517f13642e2f3dc13e7789dded42c515 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 21 Jan 2026 20:39:01 -0600 Subject: [PATCH 2/4] Add more info to GetNodeInfo --- ldk-server-protos/src/api.rs | 15 +++++++++++++++ ldk-server-protos/src/proto/api.proto | 15 +++++++++++++++ ldk-server/src/api/get_node_info.rs | 17 +++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index a4ad7f6f..56ba5e17 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -59,6 +59,21 @@ pub struct GetNodeInfoResponse { /// Will be `None` if we have no public channels or we haven’t broadcasted since the node was initialized. #[prost(uint64, optional, tag = "8")] pub latest_node_announcement_broadcast_timestamp: ::core::option::Option, + /// The addresses the node is currently listening on for incoming connections. + /// + /// Will be empty if the node is not listening on any addresses. + #[prost(string, repeated, tag = "9")] + pub listening_addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// The addresses the node announces to the network. + /// + /// Will be empty if no announcement addresses are configured. + #[prost(string, repeated, tag = "10")] + pub announcement_addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// The node alias, if configured. + /// + /// Will be `None` if no alias is configured. + #[prost(string, optional, tag = "11")] + pub node_alias: ::core::option::Option<::prost::alloc::string::String>, } /// Retrieve a new on-chain funding address. /// See more: diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index f1a191c1..e439fa03 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -49,6 +49,21 @@ message GetNodeInfoResponse { // // Will be `None` if we have no public channels or we haven’t broadcasted since the node was initialized. optional uint64 latest_node_announcement_broadcast_timestamp = 8; + + // The addresses the node is currently listening on for incoming connections. + // + // Will be empty if the node is not listening on any addresses. + repeated string listening_addresses = 9; + + // The addresses the node announces to the network. + // + // Will be empty if no announcement addresses are configured. + repeated string announcement_addresses = 10; + + // The node alias, if configured. + // + // Will be `None` if no alias is configured. + optional string node_alias = 11; } // Retrieve a new on-chain funding address. diff --git a/ldk-server/src/api/get_node_info.rs b/ldk-server/src/api/get_node_info.rs index 8599cf20..da85c727 100644 --- a/ldk-server/src/api/get_node_info.rs +++ b/ldk-server/src/api/get_node_info.rs @@ -23,6 +23,20 @@ pub(crate) fn handle_get_node_info_request( height: node_status.current_best_block.height, }; + let listening_addresses = context + .node + .listening_addresses() + .map(|addrs| addrs.into_iter().map(|a| a.to_string()).collect()) + .unwrap_or_default(); + + let announcement_addresses = context + .node + .announcement_addresses() + .map(|addrs| addrs.into_iter().map(|a| a.to_string()).collect()) + .unwrap_or_default(); + + let node_alias = context.node.node_alias().map(|alias| alias.to_string()); + let response = GetNodeInfoResponse { node_id: context.node.node_id().to_string(), current_best_block: Some(best_block), @@ -32,6 +46,9 @@ pub(crate) fn handle_get_node_info_request( latest_rgs_snapshot_timestamp: node_status.latest_rgs_snapshot_timestamp, latest_node_announcement_broadcast_timestamp: node_status .latest_node_announcement_broadcast_timestamp, + listening_addresses, + announcement_addresses, + node_alias, }; Ok(response) } From b0aa18dca43fff50f7d8678d1d76f1e6012ef3d8 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 21 Jan 2026 20:52:00 -0600 Subject: [PATCH 3/4] Add SignMessage and VerifySignature to client and CLI --- ldk-server-cli/src/main.rs | 39 ++++++++++++++++++-- ldk-server-client/src/client.rs | 30 +++++++++++++--- ldk-server-protos/src/api.rs | 50 ++++++++++++++++++++++++++ ldk-server-protos/src/endpoints.rs | 2 ++ ldk-server-protos/src/proto/api.proto | 34 ++++++++++++++++++ ldk-server/src/api/mod.rs | 2 ++ ldk-server/src/api/sign_message.rs | 22 ++++++++++++ ldk-server/src/api/verify_signature.rs | 30 ++++++++++++++++ ldk-server/src/service.rs | 21 +++++++++-- 9 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 ldk-server/src/api/sign_message.rs create mode 100644 ldk-server/src/api/verify_signature.rs diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 9c7418ec..dafa2238 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -28,9 +28,10 @@ use ldk_server_client::ldk_server_protos::api::{ GetNodeInfoRequest, GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, ListChannelsRequest, ListChannelsResponse, ListForwardedPaymentsRequest, ListPaymentsRequest, OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, - SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, UpdateChannelConfigRequest, - UpdateChannelConfigResponse, + OpenChannelRequest, OpenChannelResponse, SignMessageRequest, SignMessageResponse, + SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, + SpontaneousSendResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, + VerifySignatureRequest, VerifySignatureResponse, }; use ldk_server_client::ldk_server_protos::types::{ bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, @@ -378,6 +379,20 @@ enum Commands { )] persist: bool, }, + #[command(about = "Sign a message with the node's secret key")] + SignMessage { + #[arg(short, long, help = "The message to sign")] + message: String, + }, + #[command(about = "Verify a signature against a message and public key")] + VerifySignature { + #[arg(short, long, help = "The message that was signed")] + message: String, + #[arg(short, long, help = "The zbase32-encoded signature to verify")] + signature: String, + #[arg(short, long, help = "The hex-encoded public key of the signer")] + public_key: String, + }, #[command(about = "Generate shell completions for the CLI")] Completions { #[arg( @@ -747,6 +762,24 @@ async fn main() { client.connect_peer(ConnectPeerRequest { node_pubkey, address, persist }).await, ); }, + Commands::SignMessage { message } => { + handle_response_result::<_, SignMessageResponse>( + client + .sign_message(SignMessageRequest { message: message.into_bytes().into() }) + .await, + ); + }, + Commands::VerifySignature { message, signature, public_key } => { + handle_response_result::<_, VerifySignatureResponse>( + client + .verify_signature(VerifySignatureRequest { + message: message.into_bytes().into(), + signature, + public_key, + }) + .await, + ); + }, Commands::Completions { .. } => unreachable!("Handled above"), } } diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index e7630708..ed78fe11 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -20,16 +20,18 @@ use ldk_server_protos::api::{ ListChannelsRequest, ListChannelsResponse, ListForwardedPaymentsRequest, ListForwardedPaymentsResponse, ListPaymentsRequest, ListPaymentsResponse, OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, - SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, UpdateChannelConfigRequest, - UpdateChannelConfigResponse, + OpenChannelRequest, OpenChannelResponse, SignMessageRequest, SignMessageResponse, + SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, + SpontaneousSendResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, + VerifySignatureRequest, VerifySignatureResponse, }; use ldk_server_protos::endpoints::{ BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, - LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SPLICE_IN_PATH, - SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, + LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, + SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, + UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use prost::Message; @@ -271,6 +273,24 @@ impl LdkServerClient { self.post_request(&request, &url).await } + /// Sign a message with the node's secret key. + /// For API contract/usage, refer to docs for [`SignMessageRequest`] and [`SignMessageResponse`]. + pub async fn sign_message( + &self, request: SignMessageRequest, + ) -> Result { + let url = format!("https://{}/{SIGN_MESSAGE_PATH}", self.base_url); + self.post_request(&request, &url).await + } + + /// Verify a signature against a message and public key. + /// For API contract/usage, refer to docs for [`VerifySignatureRequest`] and [`VerifySignatureResponse`]. + pub async fn verify_signature( + &self, request: VerifySignatureRequest, + ) -> Result { + let url = format!("https://{}/{VERIFY_SIGNATURE_PATH}", self.base_url); + self.post_request(&request, &url).await + } + async fn post_request( &self, request: &Rq, url: &str, ) -> Result { diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index 56ba5e17..24f9041e 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -590,6 +590,56 @@ pub struct ListForwardedPaymentsResponse { #[prost(message, optional, tag = "2")] pub next_page_token: ::core::option::Option, } +/// Sign a message with the node's secret key. +/// See more: +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SignMessageRequest { + /// The message to sign, as raw bytes. + #[prost(bytes = "bytes", tag = "1")] + pub message: ::prost::bytes::Bytes, +} +/// The response `content` for the `SignMessage` API, when HttpStatusCode is OK (200). +/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SignMessageResponse { + /// The signature of the message, as a zbase32-encoded string. + #[prost(string, tag = "1")] + pub signature: ::prost::alloc::string::String, +} +/// Verify a signature against a message and public key. +/// See more: +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifySignatureRequest { + /// The message that was signed, as raw bytes. + #[prost(bytes = "bytes", tag = "1")] + pub message: ::prost::bytes::Bytes, + /// The signature to verify, as a zbase32-encoded string. + #[prost(string, tag = "2")] + pub signature: ::prost::alloc::string::String, + /// The hex-encoded public key of the signer. + #[prost(string, tag = "3")] + pub public_key: ::prost::alloc::string::String, +} +/// The response `content` for the `VerifySignature` API, when HttpStatusCode is OK (200). +/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifySignatureResponse { + /// Whether the signature is valid. + #[prost(bool, tag = "1")] + pub valid: bool, +} /// Retrieves an overview of all known balances. /// See more: #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/ldk-server-protos/src/endpoints.rs b/ldk-server-protos/src/endpoints.rs index bf9b6949..1ced4d63 100644 --- a/ldk-server-protos/src/endpoints.rs +++ b/ldk-server-protos/src/endpoints.rs @@ -27,3 +27,5 @@ pub const UPDATE_CHANNEL_CONFIG_PATH: &str = "UpdateChannelConfig"; pub const GET_PAYMENT_DETAILS_PATH: &str = "GetPaymentDetails"; pub const CONNECT_PEER_PATH: &str = "ConnectPeer"; pub const SPONTANEOUS_SEND_PATH: &str = "SpontaneousSend"; +pub const SIGN_MESSAGE_PATH: &str = "SignMessage"; +pub const VERIFY_SIGNATURE_PATH: &str = "VerifySignature"; diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index e439fa03..0543b1e5 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -471,6 +471,40 @@ message ListForwardedPaymentsResponse { optional types.PageToken next_page_token = 2; } +// Sign a message with the node's secret key. +// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.sign_message +message SignMessageRequest { + // The message to sign, as raw bytes. + bytes message = 1; +} + +// The response `content` for the `SignMessage` API, when HttpStatusCode is OK (200). +// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +message SignMessageResponse { + // The signature of the message, as a zbase32-encoded string. + string signature = 1; +} + +// Verify a signature against a message and public key. +// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.verify_signature +message VerifySignatureRequest { + // The message that was signed, as raw bytes. + bytes message = 1; + + // The signature to verify, as a zbase32-encoded string. + string signature = 2; + + // The hex-encoded public key of the signer. + string public_key = 3; +} + +// The response `content` for the `VerifySignature` API, when HttpStatusCode is OK (200). +// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +message VerifySignatureResponse { + // Whether the signature is valid. + bool valid = 1; +} + // Retrieves an overview of all known balances. // See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_balances message GetBalancesRequest {} diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index 5567a449..ab0487d3 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -30,9 +30,11 @@ pub(crate) mod list_payments; pub(crate) mod onchain_receive; pub(crate) mod onchain_send; pub(crate) mod open_channel; +pub(crate) mod sign_message; pub(crate) mod splice_channel; pub(crate) mod spontaneous_send; pub(crate) mod update_channel_config; +pub(crate) mod verify_signature; pub(crate) fn build_channel_config_from_proto( default_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, diff --git a/ldk-server/src/api/sign_message.rs b/ldk-server/src/api/sign_message.rs new file mode 100644 index 00000000..8ef0e015 --- /dev/null +++ b/ldk-server/src/api/sign_message.rs @@ -0,0 +1,22 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use ldk_server_protos::api::{SignMessageRequest, SignMessageResponse}; + +use crate::api::error::LdkServerError; +use crate::service::Context; + +pub(crate) fn handle_sign_message_request( + context: Context, request: SignMessageRequest, +) -> Result { + let signature = context.node.sign_message(&request.message); + + let response = SignMessageResponse { signature }; + Ok(response) +} diff --git a/ldk-server/src/api/verify_signature.rs b/ldk-server/src/api/verify_signature.rs new file mode 100644 index 00000000..9b7551ea --- /dev/null +++ b/ldk-server/src/api/verify_signature.rs @@ -0,0 +1,30 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use std::str::FromStr; + +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_server_protos::api::{VerifySignatureRequest, VerifySignatureResponse}; + +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use crate::service::Context; + +pub(crate) fn handle_verify_signature_request( + context: Context, request: VerifySignatureRequest, +) -> Result { + let public_key = PublicKey::from_str(&request.public_key).map_err(|_| { + LdkServerError::new(InvalidRequestError, "Invalid public_key provided.".to_string()) + })?; + + let valid = context.node.verify_signature(&request.message, &request.signature, &public_key); + + let response = VerifySignatureResponse { valid }; + Ok(response) +} diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index ce3aa833..cb89077f 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -22,8 +22,9 @@ use ldk_server_protos::endpoints::{ BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, - LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SPLICE_IN_PATH, - SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, + LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, + SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, + UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; use prost::Message; @@ -44,9 +45,11 @@ use crate::api::list_payments::handle_list_payments_request; use crate::api::onchain_receive::handle_onchain_receive_request; use crate::api::onchain_send::handle_onchain_send_request; use crate::api::open_channel::handle_open_channel; +use crate::api::sign_message::handle_sign_message_request; use crate::api::splice_channel::{handle_splice_in_request, handle_splice_out_request}; use crate::api::spontaneous_send::handle_spontaneous_send_request; use crate::api::update_channel_config::handle_update_channel_config_request; +use crate::api::verify_signature::handle_verify_signature_request; use crate::io::persist::paginated_kv_store::PaginatedKVStore; use crate::util::proto_adapter::to_error_response; @@ -304,6 +307,20 @@ impl Service> for NodeService { api_key, handle_spontaneous_send_request, )), + SIGN_MESSAGE_PATH => Box::pin(handle_request( + context, + req, + auth_params, + api_key, + handle_sign_message_request, + )), + VERIFY_SIGNATURE_PATH => Box::pin(handle_request( + context, + req, + auth_params, + api_key, + handle_verify_signature_request, + )), path => { let error = format!("Unknown request: {}", path).into_bytes(); Box::pin(async { From e69361ff2cc7478dfcb191f63f9a61e54853886a Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 21 Jan 2026 20:57:28 -0600 Subject: [PATCH 4/4] Add ExportPathfindingScores to client and CLI --- ldk-server-cli/src/main.rs | 26 ++++++++++--- ldk-server-client/src/client.rs | 37 ++++++++++++------- ldk-server-protos/src/api.rs | 18 +++++++++ ldk-server-protos/src/endpoints.rs | 1 + ldk-server-protos/src/proto/api.proto | 11 ++++++ .../src/api/export_pathfinding_scores.rs | 22 +++++++++++ ldk-server/src/api/mod.rs | 1 + ldk-server/src/service.rs | 18 ++++++--- 8 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 ldk-server/src/api/export_pathfinding_scores.rs diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index dafa2238..1a44f8aa 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -24,12 +24,13 @@ use ldk_server_client::ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11SendRequest, Bolt11SendResponse, Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, - ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, - GetNodeInfoRequest, GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, - ListChannelsRequest, ListChannelsResponse, ListForwardedPaymentsRequest, ListPaymentsRequest, - OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SignMessageRequest, SignMessageResponse, - SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, + ExportPathfindingScoresRequest, ForceCloseChannelRequest, ForceCloseChannelResponse, + GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, GetNodeInfoResponse, + GetPaymentDetailsRequest, GetPaymentDetailsResponse, ListChannelsRequest, ListChannelsResponse, + ListForwardedPaymentsRequest, ListPaymentsRequest, OnchainReceiveRequest, + OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, + OpenChannelResponse, SignMessageRequest, SignMessageResponse, SpliceInRequest, + SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, VerifySignatureRequest, VerifySignatureResponse, }; @@ -38,6 +39,7 @@ use ldk_server_client::ldk_server_protos::types::{ RouteParametersConfig, }; use serde::Serialize; +use serde_json::{json, Value}; use types::{CliListForwardedPaymentsResponse, CliListPaymentsResponse, CliPaginatedResponse}; mod config; @@ -393,6 +395,8 @@ enum Commands { #[arg(short, long, help = "The hex-encoded public key of the signer")] public_key: String, }, + #[command(about = "Export the pathfinding scores used by the router")] + ExportPathfindingScores, #[command(about = "Generate shell completions for the CLI")] Completions { #[arg( @@ -780,6 +784,16 @@ async fn main() { .await, ); }, + Commands::ExportPathfindingScores => { + handle_response_result::<_, Value>( + client.export_pathfinding_scores(ExportPathfindingScoresRequest {}).await.map( + |s| { + let scores_hex = s.scores.as_hex().to_string(); + json!({ "pathfinding_scores": scores_hex }) + }, + ), + ); + }, Commands::Completions { .. } => unreachable!("Handled above"), } } diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index ed78fe11..a4d19206 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -15,23 +15,23 @@ use ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11SendRequest, Bolt11SendResponse, Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, - ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, - GetNodeInfoRequest, GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, - ListChannelsRequest, ListChannelsResponse, ListForwardedPaymentsRequest, - ListForwardedPaymentsResponse, ListPaymentsRequest, ListPaymentsResponse, - OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SignMessageRequest, SignMessageResponse, - SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, - SpontaneousSendResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, - VerifySignatureRequest, VerifySignatureResponse, + ExportPathfindingScoresRequest, ExportPathfindingScoresResponse, ForceCloseChannelRequest, + ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, + GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, ListChannelsRequest, + ListChannelsResponse, ListForwardedPaymentsRequest, ListForwardedPaymentsResponse, + ListPaymentsRequest, ListPaymentsResponse, OnchainReceiveRequest, OnchainReceiveResponse, + OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, OpenChannelResponse, + SignMessageRequest, SignMessageResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest, + SpliceOutResponse, SpontaneousSendRequest, SpontaneousSendResponse, UpdateChannelConfigRequest, + UpdateChannelConfigResponse, VerifySignatureRequest, VerifySignatureResponse, }; use ldk_server_protos::endpoints::{ BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, - CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, - GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, - LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, - SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, - UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, + CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, + LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, + ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, + SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use prost::Message; @@ -291,6 +291,15 @@ impl LdkServerClient { self.post_request(&request, &url).await } + /// Export the pathfinding scores used by the router. + /// For API contract/usage, refer to docs for [`ExportPathfindingScoresRequest`] and [`ExportPathfindingScoresResponse`]. + pub async fn export_pathfinding_scores( + &self, request: ExportPathfindingScoresRequest, + ) -> Result { + let url = format!("https://{}/{EXPORT_PATHFINDING_SCORES_PATH}", self.base_url); + self.post_request(&request, &url).await + } + async fn post_request( &self, request: &Rq, url: &str, ) -> Result { diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index 24f9041e..b5b83bdf 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -640,6 +640,24 @@ pub struct VerifySignatureResponse { #[prost(bool, tag = "1")] pub valid: bool, } +/// Export the pathfinding scores used by the router. +/// See more: +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExportPathfindingScoresRequest {} +/// The response `content` for the `ExportPathfindingScores` API, when HttpStatusCode is OK (200). +/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExportPathfindingScoresResponse { + /// The serialized pathfinding scores data. + #[prost(bytes = "bytes", tag = "1")] + pub scores: ::prost::bytes::Bytes, +} /// Retrieves an overview of all known balances. /// See more: #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/ldk-server-protos/src/endpoints.rs b/ldk-server-protos/src/endpoints.rs index 1ced4d63..52cd4a50 100644 --- a/ldk-server-protos/src/endpoints.rs +++ b/ldk-server-protos/src/endpoints.rs @@ -29,3 +29,4 @@ pub const CONNECT_PEER_PATH: &str = "ConnectPeer"; pub const SPONTANEOUS_SEND_PATH: &str = "SpontaneousSend"; pub const SIGN_MESSAGE_PATH: &str = "SignMessage"; pub const VERIFY_SIGNATURE_PATH: &str = "VerifySignature"; +pub const EXPORT_PATHFINDING_SCORES_PATH: &str = "ExportPathfindingScores"; diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index 0543b1e5..f4549d0a 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -505,6 +505,17 @@ message VerifySignatureResponse { bool valid = 1; } +// Export the pathfinding scores used by the router. +// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.export_pathfinding_scores +message ExportPathfindingScoresRequest {} + +// The response `content` for the `ExportPathfindingScores` API, when HttpStatusCode is OK (200). +// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +message ExportPathfindingScoresResponse { + // The serialized pathfinding scores data. + bytes scores = 1; +} + // Retrieves an overview of all known balances. // See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_balances message GetBalancesRequest {} diff --git a/ldk-server/src/api/export_pathfinding_scores.rs b/ldk-server/src/api/export_pathfinding_scores.rs new file mode 100644 index 00000000..a6924a22 --- /dev/null +++ b/ldk-server/src/api/export_pathfinding_scores.rs @@ -0,0 +1,22 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use ldk_server_protos::api::{ExportPathfindingScoresRequest, ExportPathfindingScoresResponse}; + +use crate::api::error::LdkServerError; +use crate::service::Context; + +pub(crate) fn handle_export_pathfinding_scores_request( + context: Context, _request: ExportPathfindingScoresRequest, +) -> Result { + let scores = context.node.export_pathfinding_scores()?; + + let response = ExportPathfindingScoresResponse { scores: scores.into() }; + Ok(response) +} diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index ab0487d3..98a2c14d 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -21,6 +21,7 @@ pub(crate) mod bolt12_send; pub(crate) mod close_channel; pub(crate) mod connect_peer; pub(crate) mod error; +pub(crate) mod export_pathfinding_scores; pub(crate) mod get_balances; pub(crate) mod get_node_info; pub(crate) mod get_payment_details; diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index cb89077f..3a188767 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -20,11 +20,11 @@ use ldk_node::bitcoin::hashes::{sha256, Hash, HashEngine}; use ldk_node::Node; use ldk_server_protos::endpoints::{ BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, - CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, - GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, - LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, - SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, - UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, + CLOSE_CHANNEL_PATH, CONNECT_PEER_PATH, EXPORT_PATHFINDING_SCORES_PATH, + FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, + LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, + ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH, + SPONTANEOUS_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; use prost::Message; @@ -36,6 +36,7 @@ use crate::api::close_channel::{handle_close_channel_request, handle_force_close use crate::api::connect_peer::handle_connect_peer; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::{AuthError, InvalidRequestError}; +use crate::api::export_pathfinding_scores::handle_export_pathfinding_scores_request; use crate::api::get_balances::handle_get_balances_request; use crate::api::get_node_info::handle_get_node_info_request; use crate::api::get_payment_details::handle_get_payment_details_request; @@ -321,6 +322,13 @@ impl Service> for NodeService { api_key, handle_verify_signature_request, )), + EXPORT_PATHFINDING_SCORES_PATH => Box::pin(handle_request( + context, + req, + auth_params, + api_key, + handle_export_pathfinding_scores_request, + )), path => { let error = format!("Unknown request: {}", path).into_bytes(); Box::pin(async {