diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 9f17b1a..5dc616f 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -1,6 +1,9 @@ use clap::{Parser, Subcommand}; use ldk_server_client::client::LdkServerClient; use ldk_server_client::error::LdkServerError; +use ldk_server_client::error::LdkServerErrorCode::{ + AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError, +}; use ldk_server_client::ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt11SendRequest, Bolt12ReceiveRequest, Bolt12SendRequest, GetBalancesRequest, GetNodeInfoRequest, ListChannelsRequest, ListPaymentsRequest, @@ -123,7 +126,8 @@ async fn main() { kind: Some(bolt11_invoice_description::Kind::Hash(hash)), }), (Some(_), Some(_)) => { - handle_error(LdkServerError::InternalError( + handle_error(LdkServerError::new( + InternalError, "Only one of description or description_hash can be set.".to_string(), )); }, @@ -191,15 +195,22 @@ async fn main() { fn handle_response_result(response: Result) { match response { Ok(response) => { - println!("{:?}", response); + println!("Success: {:?}", response); }, Err(e) => { handle_error(e); }, - }; + } } fn handle_error(e: LdkServerError) -> ! { - eprintln!("Error executing command: {:?}", e); + let error_type = match e.error_code { + InvalidRequestError => "Invalid Request", + AuthError => "Authentication Error", + LightningError => "Lightning Error", + InternalServerError => "Internal Server Error", + InternalError => "Internal Error", + }; + eprintln!("Error ({}): {}", error_type, e.message); std::process::exit(1); // Exit with status code 1 on error. } diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index c8f8872..e4a519b 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -1,6 +1,9 @@ use prost::Message; use crate::error::LdkServerError; +use crate::error::LdkServerErrorCode::{ + AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError, +}; use ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11SendRequest, Bolt11SendResponse, Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, @@ -9,6 +12,7 @@ use ldk_server_protos::api::{ ListPaymentsRequest, ListPaymentsResponse, OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, OpenChannelRequest, OpenChannelResponse, }; +use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use reqwest::header::CONTENT_TYPE; use reqwest::Client; @@ -152,27 +156,46 @@ impl LdkServerClient { &self, request: &Rq, url: &str, ) -> Result { let request_body = request.encode_to_vec(); - let response_raw = match self + let response_raw = self .client .post(url) .header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) .body(request_body) .send() .await - { - Ok(response) => response, - Err(e) => { - return Err(LdkServerError::InternalError(e.to_string())); - }, - }; + .map_err(|e| { + LdkServerError::new(InternalError, format!("HTTP request failed: {}", e)) + })?; + let status = response_raw.status(); - let payload = response_raw.bytes().await?; + let payload = response_raw.bytes().await.map_err(|e| { + LdkServerError::new(InternalError, format!("Failed to read response body: {}", e)) + })?; if status.is_success() { - Ok(Rs::decode(&payload[..])?) + Ok(Rs::decode(&payload[..]).map_err(|e| { + LdkServerError::new( + InternalError, + format!("Failed to decode success response: {}", e), + ) + })?) } else { - //TODO: Error handling and error response parsing. - Err(LdkServerError::InternalError("Unknown Error".to_string())) + let error_response = ErrorResponse::decode(&payload[..]).map_err(|e| { + LdkServerError::new( + InternalError, + format!("Failed to decode error response (status {}): {}", status, e), + ) + })?; + + let error_code = match ErrorCode::from_i32(error_response.error_code) { + Some(ErrorCode::InvalidRequestError) => InvalidRequestError, + Some(ErrorCode::AuthError) => AuthError, + Some(ErrorCode::LightningError) => LightningError, + Some(ErrorCode::InternalServerError) => InternalServerError, + Some(ErrorCode::UnknownError) | None => InternalError, + }; + + Err(LdkServerError::new(error_code, error_response.message)) } } } diff --git a/ldk-server-client/src/error.rs b/ldk-server-client/src/error.rs index 37f916e..8dc16f6 100644 --- a/ldk-server-client/src/error.rs +++ b/ldk-server-client/src/error.rs @@ -1,20 +1,62 @@ -use prost::DecodeError; +use std::fmt; -/// When there is an error in request to LDK Server, the response contains a relevant error code. -#[derive(Debug)] -pub enum LdkServerError { - /// There is an unknown error. (Placeholder until error handling is done.) - InternalError(String), +/// Represents an error returned by the LDK server. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LdkServerError { + /// The error message containing a generic description of the error condition in English. + /// It is intended for a human audience only and should not be parsed to extract any information + /// programmatically. Client-side code may use it for logging only. + pub message: String, + + /// The error code uniquely identifying an error condition. + /// It is meant to be read and understood programmatically by code that detects/handles errors by + /// type. + pub error_code: LdkServerErrorCode, +} + +impl LdkServerError { + /// Creates a new [`LdkServerError`] with the given error code and message. + pub fn new(error_code: LdkServerErrorCode, message: impl Into) -> Self { + Self { error_code, message: message.into() } + } } -impl From for LdkServerError { - fn from(err: DecodeError) -> Self { - LdkServerError::InternalError(err.to_string()) +impl std::error::Error for LdkServerError {} + +impl fmt::Display for LdkServerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error: [{}]: {}", self.error_code, self.message) } } -impl From for LdkServerError { - fn from(err: reqwest::Error) -> Self { - LdkServerError::InternalError(err.to_string()) +/// Defines error codes for categorizing LDK server errors. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LdkServerErrorCode { + /// Please refer to [`ldk_server_protos::error::ErrorCode::InvalidRequestError`]. + InvalidRequestError, + + /// Please refer to [`ldk_server_protos::error::ErrorCode::AuthError`]. + AuthError, + + /// Please refer to [`ldk_server_protos::error::ErrorCode::LightningError`]. + LightningError, + + /// Please refer to [`ldk_server_protos::error::ErrorCode::InternalServerError`]. + InternalServerError, + + /// There is an unknown error, it could be a client-side bug, unrecognized error-code, network error + /// or something else. + InternalError, +} + +impl fmt::Display for LdkServerErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LdkServerErrorCode::InvalidRequestError => write!(f, "InvalidRequestError"), + LdkServerErrorCode::AuthError => write!(f, "AuthError"), + LdkServerErrorCode::LightningError => write!(f, "LightningError"), + LdkServerErrorCode::InternalServerError => write!(f, "InternalServerError"), + LdkServerErrorCode::InternalError => write!(f, "InternalError"), + } } } diff --git a/ldk-server/src/api/bolt11_receive.rs b/ldk-server/src/api/bolt11_receive.rs index f6bab7f..c81b94c 100644 --- a/ldk-server/src/api/bolt11_receive.rs +++ b/ldk-server/src/api/bolt11_receive.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use crate::util::proto_adapter::proto_to_bolt11_description; use ldk_server_protos::api::{Bolt11ReceiveRequest, Bolt11ReceiveResponse}; @@ -6,7 +7,7 @@ pub(crate) const BOLT11_RECEIVE_PATH: &str = "Bolt11Receive"; pub(crate) fn handle_bolt11_receive_request( context: Context, request: Bolt11ReceiveRequest, -) -> Result { +) -> Result { let description = proto_to_bolt11_description(request.description)?; let invoice = match request.amount_msat { Some(amount_msat) => { diff --git a/ldk-server/src/api/bolt11_send.rs b/ldk-server/src/api/bolt11_send.rs index f73f0db..7b12483 100644 --- a/ldk-server/src/api/bolt11_send.rs +++ b/ldk-server/src/api/bolt11_send.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use bytes::Bytes; use ldk_node::lightning_invoice::Bolt11Invoice; @@ -8,7 +9,7 @@ pub(crate) const BOLT11_SEND_PATH: &str = "Bolt11Send"; pub(crate) fn handle_bolt11_send_request( context: Context, request: Bolt11SendRequest, -) -> Result { +) -> Result { let invoice = Bolt11Invoice::from_str(&request.invoice.as_str()) .map_err(|_| ldk_node::NodeError::InvalidInvoice)?; diff --git a/ldk-server/src/api/bolt12_receive.rs b/ldk-server/src/api/bolt12_receive.rs index 2503987..cb1ee50 100644 --- a/ldk-server/src/api/bolt12_receive.rs +++ b/ldk-server/src/api/bolt12_receive.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use ldk_server_protos::api::{Bolt12ReceiveRequest, Bolt12ReceiveResponse}; @@ -5,7 +6,7 @@ pub(crate) const BOLT12_RECEIVE_PATH: &str = "Bolt12Receive"; pub(crate) fn handle_bolt12_receive_request( context: Context, request: Bolt12ReceiveRequest, -) -> Result { +) -> Result { let offer = match request.amount_msat { Some(amount_msat) => context.node.bolt12_payment().receive( amount_msat, diff --git a/ldk-server/src/api/bolt12_send.rs b/ldk-server/src/api/bolt12_send.rs index 95f3a48..d3ff754 100644 --- a/ldk-server/src/api/bolt12_send.rs +++ b/ldk-server/src/api/bolt12_send.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use bytes::Bytes; use ldk_node::lightning::offers::offer::Offer; @@ -8,7 +9,7 @@ pub(crate) const BOLT12_SEND_PATH: &str = "Bolt12Send"; pub(crate) fn handle_bolt12_send_request( context: Context, request: Bolt12SendRequest, -) -> Result { +) -> Result { let offer = Offer::from_str(&request.offer.as_str()).map_err(|_| ldk_node::NodeError::InvalidOffer)?; diff --git a/ldk-server/src/api/close_channel.rs b/ldk-server/src/api/close_channel.rs index b1892d7..6d7436e 100644 --- a/ldk-server/src/api/close_channel.rs +++ b/ldk-server/src/api/close_channel.rs @@ -1,3 +1,5 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::UserChannelId; @@ -8,13 +10,17 @@ pub(crate) const CLOSE_CHANNEL_PATH: &str = "CloseChannel"; pub(crate) fn handle_close_channel_request( context: Context, request: CloseChannelRequest, -) -> Result { +) -> 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)?; + let counterparty_node_id = PublicKey::from_str(&request.counterparty_node_id).map_err(|e| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid counterparty node ID, error: {}", e), + ) + })?; match request.force_close { Some(true) => context.node.force_close_channel( diff --git a/ldk-server/src/api/error.rs b/ldk-server/src/api/error.rs index d7ad95f..4fa365f 100644 --- a/ldk-server/src/api/error.rs +++ b/ldk-server/src/api/error.rs @@ -1,3 +1,7 @@ +use ldk_node::NodeError; +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct LdkServerError { // The error message containing a generic description of the error condition in English. // It is intended for a human audience only and should not be parsed to extract any information @@ -10,6 +14,21 @@ pub(crate) struct LdkServerError { pub(crate) error_code: LdkServerErrorCode, } +impl LdkServerError { + pub(crate) fn new(error_code: LdkServerErrorCode, message: impl Into) -> Self { + Self { error_code, message: message.into() } + } +} + +impl std::error::Error for LdkServerError {} + +impl fmt::Display for LdkServerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error: [{}]: {}", self.error_code, self.message) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum LdkServerErrorCode { /// Please refer to [`protos::error::ErrorCode::InvalidRequestError`]. InvalidRequestError, @@ -23,3 +42,78 @@ pub(crate) enum LdkServerErrorCode { /// Please refer to [`protos::error::ErrorCode::InternalServerError`]. InternalServerError, } + +impl fmt::Display for LdkServerErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LdkServerErrorCode::InvalidRequestError => write!(f, "InvalidRequestError"), + LdkServerErrorCode::AuthError => write!(f, "AuthError"), + LdkServerErrorCode::LightningError => write!(f, "LightningError"), + LdkServerErrorCode::InternalServerError => write!(f, "InternalServerError"), + } + } +} + +impl From for LdkServerError { + fn from(error: NodeError) -> Self { + let (message, error_code) = match error { + NodeError::InvalidAddress + | NodeError::InvalidSocketAddress + | NodeError::InvalidPublicKey + | NodeError::InvalidSecretKey + | NodeError::InvalidOfferId + | NodeError::InvalidNodeId + | NodeError::InvalidPaymentId + | NodeError::InvalidPaymentHash + | NodeError::InvalidPaymentPreimage + | NodeError::InvalidPaymentSecret + | NodeError::InvalidAmount + | NodeError::InvalidInvoice + | NodeError::InvalidOffer + | NodeError::InvalidRefund + | NodeError::InvalidChannelId + | NodeError::InvalidNetwork + | NodeError::InvalidUri + | NodeError::InvalidQuantity + | NodeError::InvalidNodeAlias + | NodeError::InvalidDateTime + | NodeError::InvalidFeeRate + | NodeError::UriParameterParsingFailed => { + (error.to_string(), LdkServerErrorCode::InvalidRequestError) + }, + + NodeError::ConnectionFailed + | NodeError::InvoiceCreationFailed + | NodeError::InvoiceRequestCreationFailed + | NodeError::OfferCreationFailed + | NodeError::RefundCreationFailed + | NodeError::PaymentSendingFailed + | NodeError::InvalidCustomTlvs + | NodeError::ProbeSendingFailed + | NodeError::ChannelCreationFailed + | NodeError::ChannelClosingFailed + | NodeError::ChannelConfigUpdateFailed + | NodeError::DuplicatePayment + | NodeError::InsufficientFunds + | NodeError::UnsupportedCurrency + | NodeError::LiquidityFeeTooHigh => (error.to_string(), LdkServerErrorCode::LightningError), + + NodeError::AlreadyRunning + | NodeError::NotRunning + | NodeError::PersistenceFailed + | NodeError::FeerateEstimationUpdateFailed + | NodeError::FeerateEstimationUpdateTimeout + | NodeError::WalletOperationFailed + | NodeError::WalletOperationTimeout + | NodeError::GossipUpdateFailed + | NodeError::GossipUpdateTimeout + | NodeError::LiquiditySourceUnavailable + | NodeError::LiquidityRequestFailed + | NodeError::OnchainTxCreationFailed + | NodeError::OnchainTxSigningFailed + | NodeError::TxSyncFailed + | NodeError::TxSyncTimeout => (error.to_string(), LdkServerErrorCode::InternalServerError), + }; + LdkServerError::new(error_code, message) + } +} diff --git a/ldk-server/src/api/get_balances.rs b/ldk-server/src/api/get_balances.rs index e236976..5516fed 100644 --- a/ldk-server/src/api/get_balances.rs +++ b/ldk-server/src/api/get_balances.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use crate::util::proto_adapter::{lightning_balance_to_proto, pending_sweep_balance_to_proto}; use ldk_server_protos::api::{GetBalancesRequest, GetBalancesResponse}; @@ -6,7 +7,7 @@ pub(crate) const GET_BALANCES: &str = "GetBalances"; pub(crate) fn handle_get_balances_request( context: Context, _request: GetBalancesRequest, -) -> Result { +) -> Result { let balance_details = context.node.list_balances(); let response = GetBalancesResponse { diff --git a/ldk-server/src/api/get_node_info.rs b/ldk-server/src/api/get_node_info.rs index 59ace45..52dff38 100644 --- a/ldk-server/src/api/get_node_info.rs +++ b/ldk-server/src/api/get_node_info.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use ldk_server_protos::api::{GetNodeInfoRequest, GetNodeInfoResponse}; use ldk_server_protos::types::BestBlock; @@ -6,7 +7,7 @@ pub(crate) const GET_NODE_INFO: &str = "GetNodeInfo"; pub(crate) fn handle_get_node_info_request( context: Context, _request: GetNodeInfoRequest, -) -> Result { +) -> Result { let node_status = context.node.status(); let best_block = BestBlock { diff --git a/ldk-server/src/api/get_payment_details.rs b/ldk-server/src/api/get_payment_details.rs index 620e7d0..033f5f8 100644 --- a/ldk-server/src/api/get_payment_details.rs +++ b/ldk-server/src/api/get_payment_details.rs @@ -1,3 +1,5 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; use crate::util::proto_adapter::payment_to_proto; use hex::FromHex; @@ -8,9 +10,14 @@ pub(crate) const GET_PAYMENT_DETAILS_PATH: &str = "GetPaymentDetails"; pub(crate) fn handle_get_payment_details_request( context: Context, request: GetPaymentDetailsRequest, -) -> Result { - let payment_id_bytes = <[u8; PaymentId::LENGTH]>::from_hex(&request.payment_id) - .map_err(|_| ldk_node::NodeError::InvalidPaymentId)?; +) -> Result { + let payment_id_bytes = + <[u8; PaymentId::LENGTH]>::from_hex(&request.payment_id).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid payment_id, must be a {}-byte hex-string.", PaymentId::LENGTH), + ) + })?; let payment_details = context.node.payment(&PaymentId(payment_id_bytes)); diff --git a/ldk-server/src/api/list_channels.rs b/ldk-server/src/api/list_channels.rs index d55f26a..712eee7 100644 --- a/ldk-server/src/api/list_channels.rs +++ b/ldk-server/src/api/list_channels.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use crate::util::proto_adapter::channel_to_proto; use ldk_server_protos::api::{ListChannelsRequest, ListChannelsResponse}; @@ -6,7 +7,7 @@ pub(crate) const LIST_CHANNELS_PATH: &str = "ListChannels"; pub(crate) fn handle_list_channels_request( context: Context, _request: ListChannelsRequest, -) -> Result { +) -> Result { let channels = context.node.list_channels().into_iter().map(|c| channel_to_proto(c)).collect(); let response = ListChannelsResponse { channels }; diff --git a/ldk-server/src/api/list_forwarded_payments.rs b/ldk-server/src/api/list_forwarded_payments.rs index 3903f7c..5396161 100644 --- a/ldk-server/src/api/list_forwarded_payments.rs +++ b/ldk-server/src/api/list_forwarded_payments.rs @@ -1,3 +1,5 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InternalServerError; use crate::io::{ FORWARDED_PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE, FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, @@ -12,7 +14,7 @@ pub(crate) const LIST_FORWARDED_PAYMENTS_PATH: &str = "ListForwardedPayments"; pub(crate) fn handle_list_forwarded_payments_request( context: Context, request: ListForwardedPaymentsRequest, -) -> Result { +) -> Result { let page_token = request.page_token.map(|p| (p.token, p.index)); let list_response = context .paginated_kv_store @@ -21,9 +23,15 @@ pub(crate) fn handle_list_forwarded_payments_request( FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, page_token, ) - .map_err(|_| ldk_node::NodeError::ConnectionFailed)?; + .map_err(|e| { + LdkServerError::new( + InternalServerError, + format!("Failed to list forwarded payments: {}", e), + ) + })?; - let mut forwarded_payments: Vec = vec![]; + let mut forwarded_payments: Vec = + Vec::with_capacity(list_response.keys.len()); for key in list_response.keys { let forwarded_payment_bytes = context .paginated_kv_store @@ -32,9 +40,19 @@ pub(crate) fn handle_list_forwarded_payments_request( FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, &key, ) - .map_err(|_| ldk_node::NodeError::ConnectionFailed)?; + .map_err(|e| { + LdkServerError::new( + InternalServerError, + format!("Failed to read forwarded payment data: {}", e), + ) + })?; let forwarded_payment = ForwardedPayment::decode(Bytes::from(forwarded_payment_bytes)) - .map_err(|_| ldk_node::NodeError::ConnectionFailed)?; + .map_err(|e| { + LdkServerError::new( + InternalServerError, + format!("Failed to decode forwarded payment: {}", e), + ) + })?; forwarded_payments.push(forwarded_payment); } let response = ListForwardedPaymentsResponse { diff --git a/ldk-server/src/api/list_payments.rs b/ldk-server/src/api/list_payments.rs index 04ef699..6aaed7a 100644 --- a/ldk-server/src/api/list_payments.rs +++ b/ldk-server/src/api/list_payments.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use crate::util::proto_adapter::payment_to_proto; use ldk_server_protos::api::{ListPaymentsRequest, ListPaymentsResponse}; @@ -6,7 +7,7 @@ pub(crate) const LIST_PAYMENTS_PATH: &str = "ListPayments"; pub(crate) fn handle_list_payments_request( context: Context, _request: ListPaymentsRequest, -) -> Result { +) -> Result { let payments = context.node.list_payments().into_iter().map(|p| payment_to_proto(p)).collect(); let response = ListPaymentsResponse { payments }; diff --git a/ldk-server/src/api/onchain_receive.rs b/ldk-server/src/api/onchain_receive.rs index bfc9af6..2c8a9d4 100644 --- a/ldk-server/src/api/onchain_receive.rs +++ b/ldk-server/src/api/onchain_receive.rs @@ -1,10 +1,11 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use ldk_server_protos::api::{OnchainReceiveRequest, OnchainReceiveResponse}; pub(crate) const ONCHAIN_RECEIVE_PATH: &str = "OnchainReceive"; pub(crate) fn handle_onchain_receive_request( context: Context, _request: OnchainReceiveRequest, -) -> Result { +) -> Result { let response = OnchainReceiveResponse { address: context.node.onchain_payment().new_address()?.to_string(), }; diff --git a/ldk-server/src/api/onchain_send.rs b/ldk-server/src/api/onchain_send.rs index a3af974..be9b2e0 100644 --- a/ldk-server/src/api/onchain_send.rs +++ b/ldk-server/src/api/onchain_send.rs @@ -1,3 +1,5 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; use ldk_node::bitcoin::{Address, FeeRate}; use ldk_server_protos::api::{OnchainSendRequest, OnchainSendResponse}; @@ -7,11 +9,16 @@ pub(crate) const ONCHAIN_SEND_PATH: &str = "OnchainSend"; pub(crate) fn handle_onchain_send_request( context: Context, request: OnchainSendRequest, -) -> Result { +) -> Result { let address = Address::from_str(&request.address) .map_err(|_| ldk_node::NodeError::InvalidAddress)? .require_network(context.node.config().network) - .map_err(|_| ldk_node::NodeError::InvalidAddress)?; + .map_err(|_| { + LdkServerError::new( + InvalidRequestError, + "Address is not valid for LdkServer's configured network.".to_string(), + ) + })?; let fee_rate = request.fee_rate_sat_per_vb.map(FeeRate::from_sat_per_vb).flatten(); let txid = match (request.amount_sats, request.send_all) { @@ -22,7 +29,12 @@ pub(crate) fn handle_onchain_send_request( (None, Some(true)) => { context.node.onchain_payment().send_all_to_address(&address, false, fee_rate)? }, - _ => return Err(ldk_node::NodeError::InvalidAmount), + _ => { + return Err(LdkServerError::new( + InvalidRequestError, + "Must specify either `send_all` or `amount_sats`, but not both or neither", + )) + }, }; let response = OnchainSendResponse { txid: txid.to_string() }; Ok(response) diff --git a/ldk-server/src/api/open_channel.rs b/ldk-server/src/api/open_channel.rs index d7a002d..3258d61 100644 --- a/ldk-server/src/api/open_channel.rs +++ b/ldk-server/src/api/open_channel.rs @@ -1,3 +1,4 @@ +use crate::api::error::LdkServerError; use crate::service::Context; use bytes::Bytes; use ldk_node::bitcoin::secp256k1::PublicKey; @@ -9,7 +10,7 @@ pub(crate) const OPEN_CHANNEL_PATH: &str = "OpenChannel"; pub(crate) fn handle_open_channel( context: Context, request: OpenChannelRequest, -) -> Result { +) -> Result { let node_id = PublicKey::from_str(&request.node_pubkey) .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; let address = SocketAddress::from_str(&request.address) diff --git a/ldk-server/src/api/update_channel_config.rs b/ldk-server/src/api/update_channel_config.rs index c183ebb..73f2786 100644 --- a/ldk-server/src/api/update_channel_config.rs +++ b/ldk-server/src/api/update_channel_config.rs @@ -1,3 +1,5 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::{InvalidRequestError, LightningError}; use crate::service::Context; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; @@ -10,9 +12,10 @@ pub(crate) const UPDATE_CHANNEL_CONFIG_PATH: &str = "UpdateChannelConfig"; pub(crate) fn handle_update_channel_config_request( context: Context, request: UpdateChannelConfigRequest, -) -> Result { - let user_channel_id: u128 = - request.user_channel_id.parse().map_err(|_| ldk_node::NodeError::InvalidChannelId)?; +) -> Result { + let user_channel_id: u128 = request.user_channel_id.parse().map_err(|_| { + LdkServerError::new(InvalidRequestError, "Failed to parse user_channel_id: invalid format.") + })?; //FIXME: Use ldk/ldk-node's partial config update api. let current_config = context @@ -20,14 +23,24 @@ pub(crate) fn handle_update_channel_config_request( .list_channels() .into_iter() .find(|c| c.user_channel_id.0 == user_channel_id) - .ok_or_else(|| ldk_node::NodeError::InvalidChannelId)? + .ok_or_else(|| { + LdkServerError::new(InvalidRequestError, "Channel not found for given user_channel_id.") + })? .config; - let updated_channel_config = - build_updated_channel_config(current_config, request.channel_config.unwrap()); + let updated_channel_config = build_updated_channel_config( + current_config, + request.channel_config.ok_or_else(|| { + LdkServerError::new(InvalidRequestError, "Channel config must be provided.") + })?, + )?; - let counterparty_node_id = PublicKey::from_str(&request.counterparty_node_id) - .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; + let counterparty_node_id = PublicKey::from_str(&request.counterparty_node_id).map_err(|e| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid counterparty node id, error {}", e), + ) + })?; context .node @@ -36,14 +49,16 @@ pub(crate) fn handle_update_channel_config_request( counterparty_node_id, updated_channel_config, ) - .map_err(ldk_node::NodeError::from)?; + .map_err(|e| { + LdkServerError::new(LightningError, format!("Failed to update channel config: {}", e)) + })?; Ok(UpdateChannelConfigResponse {}) } fn build_updated_channel_config( current_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, -) -> ChannelConfig { +) -> Result { let max_dust_htlc_exposure = proto_channel_config .max_dust_htlc_exposure .map(|max_dust_htlc_exposure| match max_dust_htlc_exposure { @@ -56,12 +71,18 @@ fn build_updated_channel_config( }) .unwrap_or(current_config.max_dust_htlc_exposure); - let cltv_expiry_delta = proto_channel_config - .cltv_expiry_delta - .map(|c| u16::try_from(c).unwrap()) - .unwrap_or(current_config.cltv_expiry_delta); + let cltv_expiry_delta = match proto_channel_config.cltv_expiry_delta { + Some(c) => Some(u16::try_from(c).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid cltv_expiry_delta, must be between 0 and {}", u16::MAX), + ) + })?), + None => None, + } + .unwrap_or_else(|| current_config.cltv_expiry_delta); - ChannelConfig { + Ok(ChannelConfig { forwarding_fee_proportional_millionths: proto_channel_config .forwarding_fee_proportional_millionths .unwrap_or(current_config.forwarding_fee_proportional_millionths), @@ -76,5 +97,5 @@ fn build_updated_channel_config( accept_underpaying_htlcs: proto_channel_config .accept_underpaying_htlcs .unwrap_or(current_config.accept_underpaying_htlcs), - } + }) } diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index 16ec58d..b3719ee 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -12,6 +12,8 @@ use crate::api::bolt11_send::{handle_bolt11_send_request, BOLT11_SEND_PATH}; use crate::api::bolt12_receive::{handle_bolt12_receive_request, BOLT12_RECEIVE_PATH}; 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::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::{ @@ -29,6 +31,7 @@ use crate::api::update_channel_config::{ handle_update_channel_config_request, UPDATE_CHANNEL_CONFIG_PATH, }; use crate::io::paginated_kv_store::PaginatedKVStore; +use crate::util::proto_adapter::to_error_response; use std::future::Future; use std::pin::Pin; use std::sync::Arc; @@ -116,7 +119,7 @@ impl Service> for NodeService { async fn handle_request< T: Message + Default, R: Message, - F: Fn(Context, T) -> Result, + F: Fn(Context, T) -> Result, >( context: Context, request: Request, handler: F, ) -> Result<>>::Response, hyper::Error> { @@ -128,16 +131,23 @@ async fn handle_request< .body(Full::new(Bytes::from(response.encode_to_vec()))) // unwrap safety: body only errors when previous chained calls failed. .unwrap()), - Err(e) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from(e.to_string().into_bytes()))) + Err(e) => { + let (error_response, status_code) = to_error_response(e); + Ok(Response::builder() + .status(status_code) + .body(Full::new(Bytes::from(error_response.encode_to_vec()))) + // unwrap safety: body only errors when previous chained calls failed. + .unwrap()) + }, + }, + Err(_) => { + let (error_response, status_code) = + to_error_response(LdkServerError::new(InvalidRequestError, "Malformed request.")); + Ok(Response::builder() + .status(status_code) + .body(Full::new(Bytes::from(error_response.encode_to_vec()))) // unwrap safety: body only errors when previous chained calls failed. - .unwrap()), + .unwrap()) }, - Err(_) => Ok(Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from(b"Error parsing request".to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()), } } diff --git a/ldk-server/src/util/proto_adapter.rs b/ldk-server/src/util/proto_adapter.rs index 82f4052..6aa2c35 100644 --- a/ldk-server/src/util/proto_adapter.rs +++ b/ldk-server/src/util/proto_adapter.rs @@ -1,5 +1,10 @@ +use crate::api::error::LdkServerError; +use crate::api::error::LdkServerErrorCode::{ + AuthError, InternalServerError, InvalidRequestError, LightningError, +}; use bytes::Bytes; use hex::prelude::*; +use hyper::StatusCode; use ldk_node::bitcoin::hashes::sha256; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; @@ -8,7 +13,8 @@ use ldk_node::lightning_invoice::{Bolt11InvoiceDescription, Description, Sha256} use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, }; -use ldk_node::{ChannelDetails, LightningBalance, NodeError, PendingSweepBalance, UserChannelId}; +use ldk_node::{ChannelDetails, LightningBalance, PendingSweepBalance, UserChannelId}; +use ldk_server_protos::error::{ErrorCode, ErrorResponse}; use ldk_server_protos::types::confirmation_status::Status::{Confirmed, Unconfirmed}; use ldk_server_protos::types::lightning_balance::BalanceType::{ ClaimableAwaitingConfirmations, ClaimableOnChannelClose, ContentiousClaimable, @@ -378,15 +384,52 @@ pub(crate) fn forwarded_payment_to_proto( pub(crate) fn proto_to_bolt11_description( description: Option, -) -> Result { +) -> Result { Ok(match description.and_then(|d| d.kind) { Some(bolt11_invoice_description::Kind::Direct(s)) => { - Bolt11InvoiceDescription::Direct(Description::new(s).unwrap()) + Bolt11InvoiceDescription::Direct(Description::new(s).map_err(|e| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid invoice description: {}", e), + ) + })?) }, Some(bolt11_invoice_description::Kind::Hash(h)) => { - let hash_bytes = <[u8; 32]>::from_hex(&h).map_err(|_| NodeError::InvalidInvoice)?; + let hash_bytes = <[u8; 32]>::from_hex(&h).map_err(|_| { + LdkServerError::new( + InvalidRequestError, + "Invalid invoice description_hash, must be 32-byte hex string".to_string(), + ) + })?; Bolt11InvoiceDescription::Hash(Sha256(*sha256::Hash::from_bytes_ref(&hash_bytes))) }, - None => Bolt11InvoiceDescription::Direct(Description::new("".to_string()).unwrap()), + None => { + Bolt11InvoiceDescription::Direct(Description::new("".to_string()).map_err(|e| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid invoice description: {}", e), + ) + })?) + }, }) } + +pub(crate) fn to_error_response(ldk_error: LdkServerError) -> (ErrorResponse, StatusCode) { + let error_code = match ldk_error.error_code { + InvalidRequestError => ErrorCode::InvalidRequestError, + AuthError => ErrorCode::AuthError, + LightningError => ErrorCode::LightningError, + InternalServerError => ErrorCode::InternalServerError, + } as i32; + + let status = match ldk_error.error_code { + InvalidRequestError => StatusCode::BAD_REQUEST, + AuthError => StatusCode::UNAUTHORIZED, + LightningError => StatusCode::INTERNAL_SERVER_ERROR, + InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, + }; + + let error_response = ErrorResponse { message: ldk_error.message, error_code }; + + (error_response, status) +}