diff --git a/README.md b/README.md index b15eafb..8ea7071 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ cargo build ### Running ``` -cargo run --bin ldk-server ./ldk-server/ldk-server.config +cargo run --bin ldk-server ./ldk-server/ldk-server-config.toml ``` Interact with the node using CLI: diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 7ebd5e9..a45ec6e 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -6,8 +6,9 @@ use ldk_server_client::error::LdkServerErrorCode::{ }; use ldk_server_client::ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt11SendRequest, Bolt12ReceiveRequest, Bolt12SendRequest, - GetBalancesRequest, GetNodeInfoRequest, ListChannelsRequest, ListPaymentsRequest, - OnchainReceiveRequest, OnchainSendRequest, OpenChannelRequest, + CloseChannelRequest, ForceCloseChannelRequest, GetBalancesRequest, GetNodeInfoRequest, + ListChannelsRequest, ListPaymentsRequest, OnchainReceiveRequest, OnchainSendRequest, + OpenChannelRequest, }; use ldk_server_client::ldk_server_protos::types::{ bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment, @@ -75,6 +76,20 @@ enum Commands { #[arg(short, long)] payer_note: Option, }, + CloseChannel { + #[arg(short, long)] + user_channel_id: String, + #[arg(short, long)] + counterparty_node_id: String, + }, + ForceCloseChannel { + #[arg(short, long)] + user_channel_id: String, + #[arg(short, long)] + counterparty_node_id: String, + #[arg(long)] + force_close_reason: Option, + }, OpenChannel { #[arg(short, long)] node_pubkey: String, @@ -170,6 +185,28 @@ async fn main() { .await, ); }, + Commands::CloseChannel { user_channel_id, counterparty_node_id } => { + handle_response_result( + client + .close_channel(CloseChannelRequest { user_channel_id, counterparty_node_id }) + .await, + ); + }, + Commands::ForceCloseChannel { + user_channel_id, + counterparty_node_id, + force_close_reason, + } => { + handle_response_result( + client + .force_close_channel(ForceCloseChannelRequest { + user_channel_id, + counterparty_node_id, + force_close_reason, + }) + .await, + ); + }, Commands::OpenChannel { node_pubkey, address, diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index e4a519b..02d9e56 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -7,10 +7,11 @@ use crate::error::LdkServerErrorCode::{ use ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11SendRequest, Bolt11SendResponse, Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, - CloseChannelRequest, CloseChannelResponse, GetBalancesRequest, GetBalancesResponse, - GetNodeInfoRequest, GetNodeInfoResponse, ListChannelsRequest, ListChannelsResponse, - ListPaymentsRequest, ListPaymentsResponse, OnchainReceiveRequest, OnchainReceiveResponse, - OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, OpenChannelResponse, + CloseChannelRequest, CloseChannelResponse, ForceCloseChannelRequest, ForceCloseChannelResponse, + GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, GetNodeInfoResponse, + ListChannelsRequest, ListChannelsResponse, ListPaymentsRequest, ListPaymentsResponse, + OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, + OpenChannelRequest, OpenChannelResponse, }; use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use reqwest::header::CONTENT_TYPE; @@ -28,6 +29,7 @@ const BOLT12_RECEIVE_PATH: &str = "Bolt12Receive"; const BOLT12_SEND_PATH: &str = "Bolt12Send"; const OPEN_CHANNEL_PATH: &str = "OpenChannel"; const CLOSE_CHANNEL_PATH: &str = "CloseChannel"; +const FORCE_CLOSE_CHANNEL_PATH: &str = "ForceCloseChannel"; const LIST_CHANNELS_PATH: &str = "ListChannels"; const LIST_PAYMENTS_PATH: &str = "ListPayments"; @@ -134,6 +136,15 @@ impl LdkServerClient { self.post_request(&request, &url).await } + /// Force closes the channel specified by given request. + /// For API contract/usage, refer to docs for [`ForceCloseChannelRequest`] and [`ForceCloseChannelResponse`]. + pub async fn force_close_channel( + &self, request: ForceCloseChannelRequest, + ) -> Result { + let url = format!("http://{}/{FORCE_CLOSE_CHANNEL_PATH}", self.base_url); + self.post_request(&request, &url).await + } + /// Retrieves list of known channels. /// For API contract/usage, refer to docs for [`ListChannelsRequest`] and [`ListChannelsResponse`]. pub async fn list_channels( diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index 770cbc3..40a22cd 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -270,9 +270,7 @@ pub struct UpdateChannelConfigRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateChannelConfigResponse {} /// Closes the channel specified by given request. -/// See more: -/// - -/// - +/// See more: #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CloseChannelRequest { @@ -282,18 +280,32 @@ pub struct CloseChannelRequest { /// The hex-encoded public key of the node to close a channel with. #[prost(string, tag = "2")] pub counterparty_node_id: ::prost::alloc::string::String, - /// Whether to force close the specified channel. - #[prost(bool, optional, tag = "3")] - pub force_close: ::core::option::Option, - /// The reason for force-closing, can only be set while force closing a channel. - #[prost(string, optional, tag = "4")] - pub force_close_reason: ::core::option::Option<::prost::alloc::string::String>, } /// The response `content` for the `CloseChannel` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CloseChannelResponse {} +/// Force closes the channel specified by given request. +/// See more: +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ForceCloseChannelRequest { + /// The local `user_channel_id` of this channel. + #[prost(string, tag = "1")] + pub user_channel_id: ::prost::alloc::string::String, + /// The hex-encoded public key of the node to close a channel with. + #[prost(string, tag = "2")] + pub counterparty_node_id: ::prost::alloc::string::String, + /// The reason for force-closing, can only be set while force closing a channel. + #[prost(string, optional, tag = "3")] + pub force_close_reason: ::core::option::Option<::prost::alloc::string::String>, +} +/// The response `content` for the `ForceCloseChannel` API, when HttpStatusCode is OK (200). +/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ForceCloseChannelResponse {} /// Returns a list of known channels. /// See more: #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index 9f61ba9..507e28e 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -261,9 +261,7 @@ message UpdateChannelConfigResponse { } // Closes the channel specified by given request. -// See more: -// - https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.close_channel -// - https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.force_close_channel +// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.close_channel message CloseChannelRequest { // The local `user_channel_id` of this channel. @@ -271,20 +269,27 @@ message CloseChannelRequest { // The hex-encoded public key of the node to close a channel with. string counterparty_node_id = 2; - - // Whether to force close the specified channel. - optional bool force_close = 3; - - // The reason for force-closing, can only be set while force closing a channel. - optional string force_close_reason = 4; } // The response `content` for the `CloseChannel` API, when HttpStatusCode is OK (200). // When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message CloseChannelResponse { +message CloseChannelResponse {} +// Force closes the channel specified by given request. +// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.force_close_channel +message ForceCloseChannelRequest { + // The local `user_channel_id` of this channel. + string user_channel_id = 1; + // The hex-encoded public key of the node to close a channel with. + string counterparty_node_id = 2; + // The reason for force-closing, can only be set while force closing a channel. + optional string force_close_reason = 3; } +// The response `content` for the `ForceCloseChannel` API, when HttpStatusCode is OK (200). +// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. +message ForceCloseChannelResponse {} + // Returns a list of known channels. // See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_channels message ListChannelsRequest {} diff --git a/ldk-server/src/api/close_channel.rs b/ldk-server/src/api/close_channel.rs index 95054a4..5b89ec6 100644 --- a/ldk-server/src/api/close_channel.rs +++ b/ldk-server/src/api/close_channel.rs @@ -1,35 +1,16 @@ -use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use crate::api::{error::LdkServerError, parse_counterparty_node_id, parse_user_channel_id}; use crate::service::Context; -use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_node::UserChannelId; use ldk_server_protos::api::{CloseChannelRequest, CloseChannelResponse}; -use std::str::FromStr; pub(crate) const CLOSE_CHANNEL_PATH: &str = "CloseChannel"; pub(crate) fn handle_close_channel_request( context: Context, request: CloseChannelRequest, ) -> Result { - let user_channel_id = - UserChannelId((&request.user_channel_id).parse::().map_err(|_| { - LdkServerError::new(InvalidRequestError, "Invalid UserChannelId.".to_string()) - })?); - let counterparty_node_id = PublicKey::from_str(&request.counterparty_node_id).map_err(|e| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid counterparty node ID, error: {}", e), - ) - })?; + let user_channel_id = parse_user_channel_id(&request.user_channel_id)?; + let counterparty_node_id = parse_counterparty_node_id(&request.counterparty_node_id)?; - match request.force_close { - Some(true) => context.node.force_close_channel( - &user_channel_id, - counterparty_node_id, - request.force_close_reason, - )?, - _ => context.node.close_channel(&user_channel_id, counterparty_node_id)?, - }; + context.node.close_channel(&user_channel_id, counterparty_node_id)?; let response = CloseChannelResponse {}; Ok(response) diff --git a/ldk-server/src/api/force_close_channel.rs b/ldk-server/src/api/force_close_channel.rs new file mode 100644 index 0000000..92feee9 --- /dev/null +++ b/ldk-server/src/api/force_close_channel.rs @@ -0,0 +1,21 @@ +use crate::api::{error::LdkServerError, parse_counterparty_node_id, parse_user_channel_id}; +use crate::service::Context; +use ldk_server_protos::api::{ForceCloseChannelRequest, ForceCloseChannelResponse}; + +pub(crate) const FORCE_CLOSE_CHANNEL_PATH: &str = "ForceCloseChannel"; + +pub(crate) fn handle_force_close_channel_request( + context: Context, request: ForceCloseChannelRequest, +) -> Result { + let user_channel_id = parse_user_channel_id(&request.user_channel_id)?; + let counterparty_node_id = parse_counterparty_node_id(&request.counterparty_node_id)?; + + context.node.force_close_channel( + &user_channel_id, + counterparty_node_id, + request.force_close_reason, + )?; + + let response = ForceCloseChannelResponse {}; + Ok(response) +} diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index c1b0fa2..6ee248a 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod bolt12_receive; pub(crate) mod bolt12_send; pub(crate) mod close_channel; pub(crate) mod error; +pub(crate) mod force_close_channel; pub(crate) mod get_balances; pub(crate) mod get_node_info; pub(crate) mod get_payment_details; @@ -14,3 +15,23 @@ pub(crate) mod onchain_receive; pub(crate) mod onchain_send; pub(crate) mod open_channel; pub(crate) mod update_channel_config; + +use crate::api::error::{LdkServerError, LdkServerErrorCode::InvalidRequestError}; +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::UserChannelId; +use std::str::FromStr; + +pub fn parse_user_channel_id(id: &str) -> Result { + Ok(UserChannelId(id.parse::().map_err(|_| { + LdkServerError::new(InvalidRequestError, "Invalid UserChannelId.".to_string()) + })?)) +} + +pub fn parse_counterparty_node_id(id: &str) -> Result { + PublicKey::from_str(id).map_err(|e| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid counterparty node ID, error: {}", e), + ) + }) +} diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index 328ded0..a0f1d66 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -14,6 +14,9 @@ use crate::api::bolt12_send::{handle_bolt12_send_request, BOLT12_SEND_PATH}; use crate::api::close_channel::{handle_close_channel_request, CLOSE_CHANNEL_PATH}; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; +use crate::api::force_close_channel::{ + handle_force_close_channel_request, FORCE_CLOSE_CHANNEL_PATH, +}; use crate::api::get_balances::{handle_get_balances_request, GET_BALANCES}; use crate::api::get_node_info::{handle_get_node_info_request, GET_NODE_INFO}; use crate::api::get_payment_details::{ @@ -85,6 +88,9 @@ impl Service> for NodeService { CLOSE_CHANNEL_PATH => { Box::pin(handle_request(context, req, handle_close_channel_request)) }, + FORCE_CLOSE_CHANNEL_PATH => { + Box::pin(handle_request(context, req, handle_force_close_channel_request)) + }, LIST_CHANNELS_PATH => { Box::pin(handle_request(context, req, handle_list_channels_request)) },