diff --git a/Cargo.lock b/Cargo.lock index 847f68a..7d1f242 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -809,6 +809,7 @@ dependencies = [ name = "ldk-node-server" version = "0.1.0" dependencies = [ + "bytes", "http-body-util", "hyper 1.4.1", "hyper-util", diff --git a/protos/src/lib.rs b/protos/src/lib.rs index 5c5fddc..3d62d28 100644 --- a/protos/src/lib.rs +++ b/protos/src/lib.rs @@ -87,15 +87,8 @@ pub struct Bolt11SendRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct Bolt11SendResponse { /// An identifier used to uniquely identify a payment. - #[prost(message, optional, tag = "1")] - pub payment_id: ::core::option::Option, -} -/// An identifier used to uniquely identify a payment. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentId { #[prost(bytes = "bytes", tag = "1")] - pub data: ::prost::bytes::Bytes, + pub payment_id: ::prost::bytes::Bytes, } /// Returns a BOLT12 offer for the given amount, if specified. /// @@ -148,8 +141,8 @@ pub struct Bolt12SendRequest { #[derive(Clone, PartialEq, ::prost::Message)] pub struct Bolt12SendResponse { /// An identifier used to uniquely identify a payment. - #[prost(message, optional, tag = "1")] - pub payment_id: ::core::option::Option, + #[prost(bytes = "bytes", tag = "1")] + pub payment_id: ::prost::bytes::Bytes, } /// Creates a new outbound channel to the given remote node. /// See more: diff --git a/protos/src/proto/ldk_node_server.proto b/protos/src/proto/ldk_node_server.proto index 07409d8..e415ba1 100644 --- a/protos/src/proto/ldk_node_server.proto +++ b/protos/src/proto/ldk_node_server.proto @@ -85,13 +85,7 @@ message Bolt11SendRequest { message Bolt11SendResponse { // An identifier used to uniquely identify a payment. - PaymentId payment_id = 1; -} - -// An identifier used to uniquely identify a payment. -message PaymentId { - - bytes data = 1; + bytes payment_id = 1; } // Returns a BOLT12 offer for the given amount, if specified. @@ -141,7 +135,7 @@ message Bolt12SendRequest { message Bolt12SendResponse { // An identifier used to uniquely identify a payment. - PaymentId payment_id = 1; + bytes payment_id = 1; } // Creates a new outbound channel to the given remote node. diff --git a/server/Cargo.toml b/server/Cargo.toml index 82fd980..285cbe2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,3 +13,4 @@ hyper-util = { version = "0.1", default-features = false, features = ["server-gr tokio = { version = "1.38.0", default-features = false, features = ["time", "signal", "rt-multi-thread"] } prost = { version = "0.11.6", default-features = false, features = ["std"] } protos = { path = "../protos" } +bytes = "1.4.0" diff --git a/server/src/api/bolt11_receive.rs b/server/src/api/bolt11_receive.rs new file mode 100644 index 0000000..8778c67 --- /dev/null +++ b/server/src/api/bolt11_receive.rs @@ -0,0 +1,21 @@ +use ldk_node::Node; +use protos::{Bolt11ReceiveRequest, Bolt11ReceiveResponse}; +use std::sync::Arc; + +pub(crate) const BOLT11_RECEIVE_PATH: &str = "Bolt11Receive"; + +pub(crate) fn handle_bolt11_receive_request( + node: Arc, request: Bolt11ReceiveRequest, +) -> Result { + let invoice = match request.amount_msat { + Some(amount_msat) => { + node.bolt11_payment().receive(amount_msat, &request.description, request.expiry_secs)? + }, + None => node + .bolt11_payment() + .receive_variable_amount(&request.description, request.expiry_secs)?, + }; + + let response = Bolt11ReceiveResponse { invoice: invoice.to_string() }; + Ok(response) +} diff --git a/server/src/api/bolt11_send.rs b/server/src/api/bolt11_send.rs new file mode 100644 index 0000000..a5d41e8 --- /dev/null +++ b/server/src/api/bolt11_send.rs @@ -0,0 +1,23 @@ +use bytes::Bytes; +use ldk_node::lightning_invoice::Bolt11Invoice; +use ldk_node::Node; +use protos::{Bolt11SendRequest, Bolt11SendResponse}; +use std::str::FromStr; +use std::sync::Arc; + +pub(crate) const BOLT11_SEND_PATH: &str = "Bolt11Send"; + +pub(crate) fn handle_bolt11_send_request( + node: Arc, request: Bolt11SendRequest, +) -> Result { + let invoice = Bolt11Invoice::from_str(&request.invoice.as_str()) + .map_err(|_| ldk_node::NodeError::InvalidInvoice)?; + + let payment_id = match request.amount_msat { + None => node.bolt11_payment().send(&invoice), + Some(amount_msat) => node.bolt11_payment().send_using_amount(&invoice, amount_msat), + }?; + + let response = Bolt11SendResponse { payment_id: Bytes::from(payment_id.0.to_vec()) }; + Ok(response) +} diff --git a/server/src/api/bolt12_receive.rs b/server/src/api/bolt12_receive.rs new file mode 100644 index 0000000..9a5ae79 --- /dev/null +++ b/server/src/api/bolt12_receive.rs @@ -0,0 +1,17 @@ +use ldk_node::Node; +use protos::{Bolt12ReceiveRequest, Bolt12ReceiveResponse}; +use std::sync::Arc; + +pub(crate) const BOLT12_RECEIVE_PATH: &str = "Bolt12Receive"; + +pub(crate) fn handle_bolt12_receive_request( + node: Arc, request: Bolt12ReceiveRequest, +) -> Result { + let offer = match request.amount_msat { + Some(amount_msat) => node.bolt12_payment().receive(amount_msat, &request.description)?, + None => node.bolt12_payment().receive_variable_amount(&request.description)?, + }; + + let response = Bolt12ReceiveResponse { offer: offer.to_string() }; + Ok(response) +} diff --git a/server/src/api/bolt12_send.rs b/server/src/api/bolt12_send.rs new file mode 100644 index 0000000..8718e0a --- /dev/null +++ b/server/src/api/bolt12_send.rs @@ -0,0 +1,25 @@ +use bytes::Bytes; +use ldk_node::lightning::offers::offer::Offer; +use ldk_node::Node; +use protos::{Bolt12SendRequest, Bolt12SendResponse}; +use std::str::FromStr; +use std::sync::Arc; + +pub(crate) const BOLT12_SEND_PATH: &str = "Bolt12Send"; + +pub(crate) fn handle_bolt12_send_request( + node: Arc, request: Bolt12SendRequest, +) -> Result { + let offer = + Offer::from_str(&request.offer.as_str()).map_err(|_| ldk_node::NodeError::InvalidOffer)?; + + let payment_id = match request.amount_msat { + None => node.bolt12_payment().send(&offer, request.payer_note), + Some(amount_msat) => { + node.bolt12_payment().send_using_amount(&offer, request.payer_note, amount_msat) + }, + }?; + + let response = Bolt12SendResponse { payment_id: Bytes::from(payment_id.0.to_vec()) }; + Ok(response) +} diff --git a/server/src/api/close_channel.rs b/server/src/api/close_channel.rs new file mode 100644 index 0000000..73e0d5c --- /dev/null +++ b/server/src/api/close_channel.rs @@ -0,0 +1,26 @@ +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::{Node, UserChannelId}; +use protos::{CloseChannelRequest, CloseChannelResponse}; +use std::str::FromStr; +use std::sync::Arc; + +pub(crate) const CLOSE_CHANNEL_PATH: &str = "CloseChannel"; + +pub(crate) fn handle_close_channel_request( + node: Arc, request: CloseChannelRequest, +) -> Result { + //TODO: Should this be string? + let mut user_channel_id_bytes = [0u8; 16]; + user_channel_id_bytes.copy_from_slice(&request.user_channel_id); + let user_channel_id = UserChannelId(u128::from_be_bytes(user_channel_id_bytes)); + let counterparty_node_id = PublicKey::from_str(&request.counterparty_node_id) + .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; + + match request.force_close { + Some(true) => node.force_close_channel(&user_channel_id, counterparty_node_id)?, + _ => node.close_channel(&user_channel_id, counterparty_node_id)?, + }; + + let response = CloseChannelResponse {}; + Ok(response) +} diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs new file mode 100644 index 0000000..42aa262 --- /dev/null +++ b/server/src/api/mod.rs @@ -0,0 +1,8 @@ +pub(crate) mod bolt11_receive; +pub(crate) mod bolt11_send; +pub(crate) mod bolt12_receive; +pub(crate) mod bolt12_send; +pub(crate) mod close_channel; +pub(crate) mod onchain_receive; +pub(crate) mod onchain_send; +pub(crate) mod open_channel; diff --git a/server/src/api/onchain_receive.rs b/server/src/api/onchain_receive.rs new file mode 100644 index 0000000..565bfd9 --- /dev/null +++ b/server/src/api/onchain_receive.rs @@ -0,0 +1,12 @@ +use ldk_node::Node; +use protos::{OnchainReceiveRequest, OnchainReceiveResponse}; +use std::sync::Arc; + +pub(crate) const ONCHAIN_RECEIVE_PATH: &str = "OnchainReceive"; +pub(crate) fn handle_onchain_receive_request( + node: Arc, _request: OnchainReceiveRequest, +) -> Result { + let response = + OnchainReceiveResponse { address: node.onchain_payment().new_address()?.to_string() }; + Ok(response) +} diff --git a/server/src/api/onchain_send.rs b/server/src/api/onchain_send.rs new file mode 100644 index 0000000..0e40d63 --- /dev/null +++ b/server/src/api/onchain_send.rs @@ -0,0 +1,25 @@ +use ldk_node::bitcoin::Address; +use ldk_node::Node; +use protos::{OnchainSendRequest, OnchainSendResponse}; +use std::str::FromStr; +use std::sync::Arc; + +pub(crate) const ONCHAIN_SEND_PATH: &str = "OnchainSend"; + +pub(crate) fn handle_onchain_send_request( + node: Arc, request: OnchainSendRequest, +) -> Result { + let address = Address::from_str(&request.address) + .map_err(|_| ldk_node::NodeError::InvalidAddress)? + .require_network(node.config().network) + .map_err(|_| ldk_node::NodeError::InvalidAddress)?; + let txid = match (request.amount_sats, request.send_all) { + (Some(amount_sats), None) => { + node.onchain_payment().send_to_address(&address, amount_sats)? + }, + (None, Some(true)) => node.onchain_payment().send_all_to_address(&address)?, + _ => return Err(ldk_node::NodeError::InvalidAmount), + }; + let response = OnchainSendResponse { txid: txid.to_string() }; + Ok(response) +} diff --git a/server/src/api/open_channel.rs b/server/src/api/open_channel.rs new file mode 100644 index 0000000..5bcb6da --- /dev/null +++ b/server/src/api/open_channel.rs @@ -0,0 +1,31 @@ +use bytes::Bytes; +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::Node; +use protos::{OpenChannelRequest, OpenChannelResponse}; +use std::str::FromStr; +use std::sync::Arc; + +pub(crate) const OPEN_CHANNEL_PATH: &str = "OpenChannel"; + +pub(crate) fn handle_open_channel( + node: Arc, request: OpenChannelRequest, +) -> Result { + let node_id = PublicKey::from_str(&request.node_pubkey) + .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; + let address = SocketAddress::from_str(&request.address) + .map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?; + let user_channel_id = node.connect_open_channel( + node_id, + address, + request.channel_amount_sats, + request.push_to_counterparty_msat, + // TODO: Allow setting ChannelConfig in open-channel. + None, + request.announce_channel, + )?; + let response = OpenChannelResponse { + user_channel_id: Bytes::from(user_channel_id.0.to_be_bytes().to_vec()), + }; + Ok(response) +} diff --git a/server/src/main.rs b/server/src/main.rs index b440903..9c9fd65 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,4 @@ +mod api; mod service; use crate::service::NodeService; diff --git a/server/src/service.rs b/server/src/service.rs index 606dfd1..ef4aa25 100644 --- a/server/src/service.rs +++ b/server/src/service.rs @@ -11,6 +11,23 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; +use crate::api::bolt11_receive::handle_bolt11_receive_request; +use crate::api::bolt11_receive::BOLT11_RECEIVE_PATH; +use crate::api::bolt11_send::handle_bolt11_send_request; +use crate::api::bolt11_send::BOLT11_SEND_PATH; +use crate::api::bolt12_receive::handle_bolt12_receive_request; +use crate::api::bolt12_receive::BOLT12_RECEIVE_PATH; +use crate::api::bolt12_send::handle_bolt12_send_request; +use crate::api::bolt12_send::BOLT12_SEND_PATH; +use crate::api::close_channel::handle_close_channel_request; +use crate::api::close_channel::CLOSE_CHANNEL_PATH; +use crate::api::onchain_receive::handle_onchain_receive_request; +use crate::api::onchain_receive::ONCHAIN_RECEIVE_PATH; +use crate::api::onchain_send::handle_onchain_send_request; +use crate::api::onchain_send::ONCHAIN_SEND_PATH; +use crate::api::open_channel::handle_open_channel; +use crate::api::open_channel::OPEN_CHANNEL_PATH; + #[derive(Clone)] pub struct NodeService { node: Arc, @@ -28,8 +45,22 @@ impl Service> for NodeService { type Future = Pin> + Send>>; fn call(&self, req: Request) -> Self::Future { - let _node = Arc::clone(&self.node); + let node = Arc::clone(&self.node); match req.uri().path() { + ONCHAIN_RECEIVE_PATH => { + Box::pin(handle_request(node, req, handle_onchain_receive_request)) + }, + ONCHAIN_SEND_PATH => Box::pin(handle_request(node, req, handle_onchain_send_request)), + BOLT11_RECEIVE_PATH => { + Box::pin(handle_request(node, req, handle_bolt11_receive_request)) + }, + BOLT11_SEND_PATH => Box::pin(handle_request(node, req, handle_bolt11_send_request)), + BOLT12_RECEIVE_PATH => { + Box::pin(handle_request(node, req, handle_bolt12_receive_request)) + }, + BOLT12_SEND_PATH => Box::pin(handle_request(node, req, handle_bolt12_send_request)), + OPEN_CHANNEL_PATH => Box::pin(handle_request(node, req, handle_open_channel)), + CLOSE_CHANNEL_PATH => Box::pin(handle_request(node, req, handle_close_channel_request)), path => { let error = format!("Unknown request: {}", path).into_bytes(); Box::pin(async {