Skip to content

Commit

Permalink
Handle specific errors from the LSP upon invoice
Browse files Browse the repository at this point in the history
  • Loading branch information
TonyGiorgio committed Jul 6, 2023
1 parent 321d755 commit ba4f117
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 20 deletions.
11 changes: 9 additions & 2 deletions mutiny-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ pub enum MutinyError {
/// Failed to call on the given LNURL
#[error("Failed to call on the given LNURL.")]
LnUrlFailure,
#[error("Failed to connect to LSP.")]
LspFailure,
/// Could not make a request to the LSP.
#[error("Failed to make a request to the LSP.")]
LspGenericError,
/// LSP indicated it could not fund the channel requested.
#[error("Failed to request channel from LSP due to funding error.")]
LspFundingError,
/// LSP indicated it was not connected to the client node.
#[error("Failed to have a connection to the LSP node.")]
LspConnectionError,
/// No route for the given target could be found.
#[error("Failed to find route.")]
RoutingFailed,
Expand Down
59 changes: 45 additions & 14 deletions mutiny-core/src/lspclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,27 @@ pub struct FeeResponse {
pub fee_amount_msat: u64,
}

#[derive(Deserialize, Debug)]
struct ErrorResponse {
error: String,
message: String,
}

const GET_INFO_PATH: &str = "/api/v1/info";
const PROPOSAL_PATH: &str = "/api/v1/proposal";
const FEE_PATH: &str = "/api/v1/fee";

impl LspClient {
pub async fn new(url: &str) -> anyhow::Result<Self> {
pub async fn new(url: &str) -> Result<Self, MutinyError> {
let http_client = Client::new();
let get_info_response: GetInfoResponse = http_client
.get(format!("{}{}", url, GET_INFO_PATH))
.send()
.await
.map_err(|_| MutinyError::LspFailure)?
.map_err(|_| MutinyError::LspGenericError)?
.json()
.await
.map_err(|_| MutinyError::LspFailure)?;
.map_err(|_| MutinyError::LspGenericError)?;

let connection_string = get_info_response
.connection_methods
Expand Down Expand Up @@ -114,38 +120,63 @@ impl LspClient {
})
}

pub(crate) async fn get_lsp_invoice(&self, bolt11: String) -> anyhow::Result<String> {
pub(crate) async fn get_lsp_invoice(&self, bolt11: String) -> Result<String, MutinyError> {
let payload = ProposalRequest {
bolt11,
host: None,
port: None,
};

let proposal_response: ProposalResponse = self
let response: reqwest::Response = self
.http_client
.post(format!("{}{}", &self.url, PROPOSAL_PATH))
.json(&payload)
.send()
.await
.map_err(|_| MutinyError::LspFailure)?
.json()
.await
.map_err(|_| MutinyError::LspFailure)?;

Ok(proposal_response.jit_bolt11)
.map_err(|_| MutinyError::LspGenericError)?;

if response.status().as_u16() == 200 {
let proposal_response: ProposalResponse = response
.json()
.await
.map_err(|_| MutinyError::LspGenericError)?;

return Ok(proposal_response.jit_bolt11);
} else {
// If it's not a 200 status, copy the response body to a string and try to parse as ErrorResponse
let response_body = response
.text()
.await
.map_err(|_| MutinyError::LspGenericError)?;

if let Ok(error_body) = serde_json::from_str::<ErrorResponse>(&response_body) {
if error_body.error == "Internal Server Error" {
if error_body.message == "Cannot fund new channel at this time" {
return Err(MutinyError::LspFundingError);
} else if error_body.message.starts_with("Failed to connect to peer") {
return Err(MutinyError::LspConnectionError);
}
}
}
}

Err(MutinyError::LspGenericError)
}

pub(crate) async fn get_lsp_fee_msat(&self, fee_request: FeeRequest) -> anyhow::Result<u64> {
pub(crate) async fn get_lsp_fee_msat(
&self,
fee_request: FeeRequest,
) -> Result<u64, MutinyError> {
let fee_response: FeeResponse = self
.http_client
.post(format!("{}{}", &self.url, FEE_PATH))
.json(&fee_request)
.send()
.await
.map_err(|_| MutinyError::LspFailure)?
.map_err(|_| MutinyError::LspGenericError)?
.json()
.await
.map_err(|_| MutinyError::LspFailure)?;
.map_err(|_| MutinyError::LspGenericError)?;

Ok(fee_response.fee_amount_msat)
}
Expand Down
20 changes: 20 additions & 0 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,26 @@ impl<S: MutinyStorage> NodeManager<S> {
}

/// Creates a BIP 21 invoice. This creates a new address and a lightning invoice.
/// The lightning invoice may return errors related to the LSP. Check the error and
/// fallback to `get_new_address` and warn the user that Lightning is not available.
///
/// Errors that might be returned include:
///
/// - [`MutinyError::LspGenericError`]: This is returned for various reasons, including if a
/// request to the LSP server fails for any reason, or if the server returns
/// a status other than 500 that can't be parsed into a `ProposalResponse`.
///
/// - [`MutinyError::LspFundingError`]: Returned if the LSP server returns an error with
/// a status of 500, indicating an "Internal Server Error", and a message
/// stating "Cannot fund new channel at this time". This means that the LSP cannot support
/// a new channel at this time.
///
/// - [`MutinyError::LspConnectionError`]: Returned if the LSP server returns an error with
/// a status of 500, indicating an "Internal Server Error", and a message that starts with
/// "Failed to connect to peer". This means that the LSP is not connected to our node.
///
/// If the server returns a status of 500 with a different error message,
/// a [`MutinyError::LspGenericError`] is returned.
pub async fn create_bip21(
&self,
amount: Option<u64>,
Expand Down
2 changes: 1 addition & 1 deletion mutiny-core/src/redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl<S: MutinyStorage> RedshiftManager for NodeManager<S> {
let node = self.get_node(&node.pubkey).await?;
match &node.lsp_client {
Some(lsp) => lsp.pubkey,
None => return Err(MutinyError::LspFailure),
None => return Err(MutinyError::LspGenericError),
}
}
};
Expand Down
15 changes: 12 additions & 3 deletions mutiny-wasm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,15 @@ pub enum MutinyJsError {
/// Failed to call on the given LNURL
#[error("Failed to call on the given LNURL.")]
LnUrlFailure,
#[error("Failed to connect to LSP.")]
LspFailure,
/// Could not make a request to the LSP.
#[error("Failed to make a request to the LSP.")]
LspGenericError,
/// LSP indicated it could not fund the channel requested.
#[error("Failed to request channel from LSP due to funding error.")]
LspFundingError,
/// LSP indicated it was not connected to the client node.
#[error("Failed to have a connection to the LSP node.")]
LspConnectionError,
/// Called incorrect lnurl function, eg calling withdraw on a pay lnurl
#[error("Called incorrect lnurl function.")]
IncorrectLnUrlFunction,
Expand Down Expand Up @@ -133,7 +140,9 @@ impl From<MutinyError> for MutinyJsError {
MutinyError::ReserveAmountError => MutinyJsError::ReserveAmountError,
MutinyError::InsufficientBalance => MutinyJsError::InsufficientBalance,
MutinyError::LnUrlFailure => MutinyJsError::LnUrlFailure,
MutinyError::LspFailure => MutinyJsError::LspFailure,
MutinyError::LspGenericError => MutinyJsError::LspGenericError,
MutinyError::LspFundingError => MutinyJsError::LspFundingError,
MutinyError::LspConnectionError => MutinyJsError::LspConnectionError,
MutinyError::RoutingFailed => MutinyJsError::RoutingFailed,
MutinyError::PeerInfoParseFailed => MutinyJsError::PeerInfoParseFailed,
MutinyError::ChannelCreationFailed => MutinyJsError::ChannelCreationFailed,
Expand Down
21 changes: 21 additions & 0 deletions mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ impl MutinyWallet {
}

/// Creates a BIP 21 invoice. This creates a new address and a lightning invoice.
/// The lightning invoice may return errors related to the LSP. Check the error and
/// fallback to `get_new_address` and warn the user that Lightning is not available.
///
///
/// Errors that might be returned include:
///
/// - [`MutinyJsError::LspGenericError`]: This is returned for various reasons, including if a
/// request to the LSP server fails for any reason, or if the server returns
/// a status other than 500 that can't be parsed into a `ProposalResponse`.
///
/// - [`MutinyJsError::LspFundingError`]: Returned if the LSP server returns an error with
/// a status of 500, indicating an "Internal Server Error", and a message
/// stating "Cannot fund new channel at this time". This means that the LSP cannot support
/// a new channel at this time.
///
/// - [`MutinyJsError::LspConnectionError`]: Returned if the LSP server returns an error with
/// a status of 500, indicating an "Internal Server Error", and a message that starts with
/// "Failed to connect to peer". This means that the LSP is not connected to our node.
///
/// If the server returns a status of 500 with a different error message,
/// a [`MutinyJsError::LspGenericError`] is returned.
#[wasm_bindgen]
pub async fn create_bip21(
&self,
Expand Down

0 comments on commit ba4f117

Please sign in to comment.