Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chain service error #989

Merged
merged 9 commits into from
May 27, 2024
11 changes: 9 additions & 2 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1763,9 +1763,16 @@ impl BreezServices {
&self,
channel: &crate::models::Channel,
) -> Result<(Option<u64>, Option<String>)> {
let maybe_outspend = self
let maybe_outspend_res = self
.lookup_chain_service_closing_outspend(channel.clone())
.await?;
.await;
let maybe_outspend: Option<Outspend> = match maybe_outspend_res {
Ok(s) => s,
Err(e) => {
error!("Failed to lookup channel closing data: {:?}", e);
None
}
};

let maybe_closed_at = maybe_outspend
.clone()
Expand Down
44 changes: 30 additions & 14 deletions libs/sdk-core/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use crate::bitcoin::hashes::hex::FromHex;
use crate::bitcoin::{OutPoint, Txid};
use crate::error::{SdkError, SdkResult};
use crate::input_parser::{get_parse_and_log_response, get_reqwest_client, post_and_log_response};
use crate::input_parser::{get_parse_and_log_response, post_and_log_response};

pub const DEFAULT_MEMPOOL_SPACE_URL: &str = "https://mempool.space/api";

Expand Down Expand Up @@ -337,27 +337,20 @@ impl MempoolSpace {
#[tonic::async_trait]
impl ChainService for MempoolSpace {
async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
get_parse_and_log_response(&format!("{}/v1/fees/recommended", self.base_url)).await
get_parse_and_log_response(&format!("{}/v1/fees/recommended", self.base_url), true).await
}

async fn address_transactions(&self, address: String) -> SdkResult<Vec<OnchainTx>> {
get_parse_and_log_response(&format!("{}/address/{address}/txs", self.base_url)).await
get_parse_and_log_response(&format!("{}/address/{address}/txs", self.base_url), true).await
}

async fn current_tip(&self) -> SdkResult<u32> {
get_parse_and_log_response(&format!("{}/blocks/tip/height", self.base_url)).await
get_parse_and_log_response(&format!("{}/blocks/tip/height", self.base_url), true).await
}

async fn transaction_outspends(&self, txid: String) -> SdkResult<Vec<Outspend>> {
let url = format!("{}/tx/{txid}/outspends", self.base_url);
Ok(get_reqwest_client()?
.get(url)
.send()
.await
.map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?
.json()
.await
.map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?)
get_parse_and_log_response(&url, true).await
}

async fn broadcast_transaction(&self, tx: Vec<u8>) -> SdkResult<String> {
Expand All @@ -373,8 +366,9 @@ impl ChainService for MempoolSpace {
}
#[cfg(test)]
mod tests {
use crate::chain::{
MempoolSpace, OnchainTx, RedundantChainService, RedundantChainServiceTrait,
use crate::{
chain::{MempoolSpace, OnchainTx, RedundantChainService, RedundantChainServiceTrait},
error::SdkError,
};
use anyhow::Result;
use tokio::test;
Expand Down Expand Up @@ -437,6 +431,28 @@ mod tests {

assert_eq!(expected_serialized, serialized_res);

let outspends = ms
.transaction_outspends(
"5e0668bf1cd24f2f8656ee82d4886f5303a06b26838e24b7db73afc59e228985".to_string(),
)
.await?;
assert_eq!(outspends.len(), 2);

let outspends = ms
.transaction_outspends(
"07c9d3fbffc20f96ea7c93ef3bcdf346c8a8456c25850ea76be62b24a7cf6901".to_string(),
)
.await;
match outspends {
Ok(_) => panic!("Expected an error"),
Err(e) => match e {
SdkError::ServiceConnectivity { err } => {
assert_eq!(err, "GET request https://mempool.space/api/tx/07c9d3fbffc20f96ea7c93ef3bcdf346c8a8456c25850ea76be62b24a7cf6901/outspends failed with status: 404 Not Found")
}
_ => panic!("Expected a service connectivity error"),
},
};

Ok(())
}

Expand Down
57 changes: 41 additions & 16 deletions libs/sdk-core/src/input_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::time::Duration;

use anyhow::{anyhow, Result};
use bip21::Uri;
use reqwest::StatusCode;
use serde::Deserialize;
use serde::Serialize;

Expand Down Expand Up @@ -249,30 +250,49 @@ pub(crate) async fn post_and_log_response(url: &str, body: Option<String>) -> Sd
/// Makes a GET request to the specified `url` and logs on DEBUG:
/// - the URL
/// - the raw response body
pub(crate) async fn get_and_log_response(url: &str) -> SdkResult<String> {
/// - the response HTTP status code
pub(crate) async fn get_and_log_response(url: &str) -> SdkResult<(String, StatusCode)> {
debug!("Making GET request to: {url}");

let raw_body = get_reqwest_client()?
let response = get_reqwest_client()?
.get(url)
.send()
.await
.map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?
.map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?;
let status = response.status();
let raw_body = response
.text()
.await
.map_err(|e| SdkError::ServiceConnectivity { err: e.to_string() })?;
debug!("Received raw response body: {raw_body}");
debug!("Received response, status: {status}, raw response body: {raw_body}");

Ok(raw_body)
Ok((raw_body, status))
}

/// Wrapper around [get_and_log_response] that, in addition, parses the payload into an expected type
pub(crate) async fn get_parse_and_log_response<T>(url: &str) -> SdkResult<T>
/// Wrapper around [get_and_log_response] that, in addition, parses the payload into an expected type.
///
/// ### Arguments
///
/// - `url`: the URL on which GET will be called
/// - `enforce_status_check`: if true, the HTTP status code is checked in addition to trying to
/// parse the payload. In this case, an HTTP error code will automatically cause this function to
/// return `Err`, regardless of the payload. If false, the result type will be determined only
/// by the result of parsing the payload into the desired target type.
pub(crate) async fn get_parse_and_log_response<T>(
Copy link
Contributor

@erdemyerebasmaz erdemyerebasmaz May 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to documentation, I think it makes much more sense to rename this parse_get_and_log_response

url: &str,
enforce_status_check: bool,
) -> SdkResult<T>
where
for<'a> T: serde::de::Deserialize<'a>,
{
let raw_body = get_and_log_response(url).await?;
let (raw_body, status) = get_and_log_response(url).await?;
if enforce_status_check && !status.is_success() {
let err = format!("GET request {url} failed with status: {status}");
error!("{err}");
return Err(SdkError::ServiceConnectivity { err });
}

Ok(serde_json::from_str(&raw_body)?)
serde_json::from_str::<T>(&raw_body).map_err(Into::into)
}

/// Prepends the given prefix to the input, if the input doesn't already start with it
Expand Down Expand Up @@ -416,7 +436,7 @@ async fn resolve_lnurl(
}

lnurl_endpoint = maybe_replace_host_with_mockito_test_host(lnurl_endpoint)?;
let lnurl_data: LnUrlRequestData = get_parse_and_log_response(&lnurl_endpoint)
let lnurl_data: LnUrlRequestData = get_parse_and_log_response(&lnurl_endpoint, false)
.await
.map_err(|_| anyhow!("Failed to parse response"))?;
let temp = lnurl_data.into();
Expand Down Expand Up @@ -1055,15 +1075,20 @@ pub(crate) mod tests {
}
"#.replace('\n', "");

let response_body = match return_lnurl_error {
None => expected_lnurl_withdraw_data,
Some(err_reason) => {
["{\"status\": \"ERROR\", \"reason\": \"", &err_reason, "\"}"].join("")
}
let (response_body, status) = match &return_lnurl_error {
None => (expected_lnurl_withdraw_data, 200),
Some(err_reason) => (
["{\"status\": \"ERROR\", \"reason\": \"", err_reason, "\"}"].join(""),
400,
),
};

let mut server = MOCK_HTTP_SERVER.lock().unwrap();
server.mock("GET", path).with_body(response_body).create()
server
.mock("GET", path)
.with_body(response_body)
.with_status(status)
.create()
}

#[tokio::test]
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/src/lnurl/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub(crate) async fn perform_lnurl_auth(
.query_pairs_mut()
.append_pair("key", &linking_keys.public_key().to_hex());

get_parse_and_log_response(callback_url.as_ref())
get_parse_and_log_response(callback_url.as_ref(), false)
.await
.map_err(|e| LnUrlError::ServiceConnectivity(e.to_string()))
}
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/src/lnurl/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub(crate) async fn validate_lnurl_pay(
)?;

let callback_url = build_pay_callback_url(user_amount_msat, comment, req_data)?;
let callback_resp_text = get_and_log_response(&callback_url)
let (callback_resp_text, _) = get_and_log_response(&callback_url)
.await
.map_err(|e| LnUrlError::ServiceConnectivity(e.to_string()))?;

Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/src/lnurl/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub(crate) async fn validate_lnurl_withdraw(

// Send invoice to the LNURL-w endpoint via the callback
let callback_url = build_withdraw_callback_url(&req_data, &invoice)?;
let callback_res: LnUrlCallbackStatus = get_parse_and_log_response(&callback_url)
let callback_res: LnUrlCallbackStatus = get_parse_and_log_response(&callback_url, false)
.await
.map_err(|e| LnUrlError::ServiceConnectivity(e.to_string()))?;
let withdraw_status = match callback_res {
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/src/swap_out/boltzswap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ impl ReverseSwapServiceAPI for BoltzApi {
}

pub async fn reverse_swap_pair_info() -> ReverseSwapResult<ReverseSwapPairInfo> {
let pairs: Pairs = get_parse_and_log_response(GET_PAIRS_ENDPOINT).await?;
let pairs: Pairs = get_parse_and_log_response(GET_PAIRS_ENDPOINT, true).await?;
match pairs.pairs.get("BTC/BTC") {
None => Err(ReverseSwapError::generic("BTC pair not found")),
Some(btc_pair) => {
Expand Down
Loading