diff --git a/payjoin/Cargo.toml b/payjoin/Cargo.toml index fb593748..81e122f7 100644 --- a/payjoin/Cargo.toml +++ b/payjoin/Cargo.toml @@ -30,9 +30,9 @@ bhttp = { version = "0.4.0", optional = true } rand = { version = "0.8.4", optional = true } serde = { version = "1.0.186", default-features = false } url = "2.2.2" +bitcoind = { version = "0.31.1", features = ["0_21_2"] } [dev-dependencies] -bitcoind = { version = "0.31.1", features = ["0_21_2"] } env_logger = "0.9.0" rustls = "0.21.9" testcontainers = "0.15.0" @@ -42,3 +42,8 @@ ureq = "2.8.0" [package.metadata.docs.rs] features = ["send", "receive", "base64"] +env_logger = "0.9.0" + + +[lib] +doctest = false diff --git a/payjoin/src/receive/error.rs b/payjoin/src/receive/error.rs index 5de611bd..5dc61999 100644 --- a/payjoin/src/receive/error.rs +++ b/payjoin/src/receive/error.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Display}; pub enum Error { /// To be returned as HTTP 400 BadRequest(RequestError), - // To be returned as HTTP 500 + /// To be returned as HTTP 500 Server(Box), // V2 d/encapsulation failed #[cfg(feature = "v2")] diff --git a/payjoin/src/send/error.rs b/payjoin/src/send/error.rs index 7be1fc31..a8aadad6 100644 --- a/payjoin/src/send/error.rs +++ b/payjoin/src/send/error.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use bitcoin::locktime::absolute::LockTime; use bitcoin::Sequence; +use bitcoind::bitcoincore_rpc::jsonrpc::serde_json::Value; use crate::input_type::{InputType, InputTypeError}; @@ -238,18 +239,19 @@ impl From for CreateRequestError { } pub enum ResponseError { - /// `WellKnown` errors following the BIP78 spec - /// https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#user-content-Receivers_well_known_errors + /// `WellKnown` errors following the BIP78 spec. + /// /// These errors are displayed to end users. /// - /// The `WellKnownError` represents `errorCode` and `message`. - /// The `String` is a custom message that can be used for debug logs. - WellKnown(WellKnownError, String), - /// `Unrecognized` errors are errors that are not well known and are only displayed in debug logs. + /// `WellKnownError` represents `errorCode` and `message`. + /// `String` is a custom message that can be used for debug logs. + WellKnown(WellKnownError, Option), + /// `Unrecognized` errors are errors that are not + /// mentioned in the spec and are only displayed in debug logs. /// They are not displayed to end users. /// - /// The first `String` is `errorCode` - /// The second `String` is `message`. + /// First `String` is `errorCode` + /// Second `String` is `message`. Unrecognized(String, String), /// `Validation` errors are errors that are caused by malformed responses. /// They are only displayed in debug logs. @@ -281,7 +283,8 @@ impl Debug for ResponseError { f, r#"Well known error: {{ "errorCode": "{}", "message": "{}" }}"#, - e, msg + e, + msg.clone().unwrap_or("".to_string()) ), Self::Unrecognized(code, msg) => write!( f, @@ -293,6 +296,8 @@ impl Debug for ResponseError { } } +impl std::error::Error for ResponseError {} + pub enum WellKnownError { Unavailable, NotEnoughMoney, @@ -335,4 +340,54 @@ impl WellKnownError { Self::OriginalPsbtRejected => "The receiver rejected the original PSBT.", } } + + pub fn error_code_from_json(json: Value) -> Result { + let error_code = json + .as_object() + .and_then(|v| v.get("errorCode")) + .and_then(|v| v.as_str()) + .ok_or(InternalValidationError::Parse)?; + WellKnownError::from_str(error_code).map_err(|_| { + let message = match json.get("message") { + Some(v) => v.to_string(), + None => { + log::debug!("Unrecognized Error detected, {}", json); + "Unrecognized Error detected".to_string() + } + }; + ResponseError::Unrecognized(error_code.to_string(), message) + }) + } + + pub fn error_code_from_str(error_code: &str) -> Result { + Self::error_code_from_json(Value::from_str(&error_code).map_err(|e| { + log::debug!("Invalid json detected, {}", e); + InternalValidationError::Parse + })?) + } +} + +#[cfg(test)] +mod tests { + use bitcoind::bitcoincore_rpc::jsonrpc::serde_json::json; + + use super::*; + + #[test] + fn test_parse_json() { + let json_error = json!({ + "errorCode": "version-unsupported", + "message": "This version of payjoin is not supported." + }); + assert_eq!( + WellKnownError::error_code_from_json(json_error).unwrap().to_string(), + WellKnownError::VersionUnsupported.to_string() + ); + let str_error = "{\"errorCode\":\"version-unsupported\", + \"message\":\"This version of payjoin is not supported.\"}"; + assert_eq!( + WellKnownError::error_code_from_str(str_error).unwrap().to_string(), + WellKnownError::VersionUnsupported.to_string() + ); + } } diff --git a/payjoin/src/send/mod.rs b/payjoin/src/send/mod.rs index 157e5cb3..b6c5f55e 100644 --- a/payjoin/src/send/mod.rs +++ b/payjoin/src/send/mod.rs @@ -155,7 +155,7 @@ use crate::PjUri; #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] compile_error!("This crate currently only supports 32 bit and 64 bit architectures"); -mod error; +pub mod error; type InternalResult = Result; @@ -536,7 +536,7 @@ impl ContextV1 { let proposal = Psbt::from_str(&res_str).or_else(|_| { // That wasn't a valid PSBT. Maybe it's a valid error response? match WellKnownError::error_code_from_str(&res_str) { - Ok(well_known) => return Err(ResponseError::WellKnown(well_known, "".to_string())), + Ok(well_known) => return Err(ResponseError::WellKnown(well_known, None)), Err(e) => Err(e), } })?; @@ -907,6 +907,7 @@ fn serialize_v2_body( disable_output_substitution, fee_contribution, min_feerate, + 2, ) .map_err(InternalCreateRequestError::Url)?; let query_params = placeholder_url.query().unwrap_or_default(); @@ -938,6 +939,11 @@ fn serialize_url( Ok(url) } +pub fn serialize_psbt(psbt: &Psbt) -> Vec { + let bytes = psbt.serialize(); + bitcoin::base64::encode(bytes).into_bytes() +} + #[cfg(test)] mod tests { #[test]