From eaa17373aa6ca42ba2b8d3097e423b008bb20e61 Mon Sep 17 00:00:00 2001 From: Erik De Smedt Date: Fri, 15 Sep 2023 11:18:13 +0200 Subject: [PATCH] Mapping of JsonRPCErrorCodes Allow the mapping of error_codes defined in JsonRpc --- libs/gl-client/src/lsps/error.rs | 4 +- libs/gl-client/src/lsps/json_rpc.rs | 70 +++++++++++++++++----- libs/gl-client/src/lsps/json_rpc_erased.rs | 24 ++++---- libs/gl-client/src/lsps/message.rs | 70 ++++++++++++++++------ libs/gl-client/src/lsps/mod.rs | 1 + 5 files changed, 123 insertions(+), 46 deletions(-) diff --git a/libs/gl-client/src/lsps/error.rs b/libs/gl-client/src/lsps/error.rs index d176af692..d528fa166 100644 --- a/libs/gl-client/src/lsps/error.rs +++ b/libs/gl-client/src/lsps/error.rs @@ -24,7 +24,7 @@ impl From for LspsError { } } -pub fn map_json_rpc_error_code_to_str(code : i64) -> &'static str { +pub fn map_json_rpc_error_code_to_str(code: i64) -> &'static str { match code { -32700 => "parsing_error", -32600 => "invalid_request", @@ -32,7 +32,7 @@ pub fn map_json_rpc_error_code_to_str(code : i64) -> &'static str { -32602 => "invalid_params", -32603 => "internal_error", -32099..=-32000 => "implementation_defined_server_error", - _ => "unknown_error_code" + _ => "unknown_error_code", } } diff --git a/libs/gl-client/src/lsps/json_rpc.rs b/libs/gl-client/src/lsps/json_rpc.rs index 06f34231e..93a69abc6 100644 --- a/libs/gl-client/src/lsps/json_rpc.rs +++ b/libs/gl-client/src/lsps/json_rpc.rs @@ -3,6 +3,8 @@ use serde::de::DeserializeOwned; use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; +use super::error::map_json_rpc_error_code_to_str; + /// Generate a random json_rpc_id string that follows the requirements of LSPS0 /// /// - Should be a String @@ -15,7 +17,10 @@ pub fn generate_random_rpc_id() -> String { } #[derive(Debug, Serialize, Deserialize)] -pub struct JsonRpcMethod { +pub struct JsonRpcMethod +where + E: MapErrorCode, +{ pub method: &'static str, #[serde(skip_serializing)] request: std::marker::PhantomData, @@ -25,7 +30,10 @@ pub struct JsonRpcMethod { error_type: std::marker::PhantomData, } -impl JsonRpcMethod { +impl JsonRpcMethod +where + E: MapErrorCode, +{ pub const fn new(method: &'static str) -> Self { return Self { method: method, @@ -49,13 +57,19 @@ impl JsonRpcMethod { } } -impl JsonRpcMethod { +impl JsonRpcMethod +where + E: MapErrorCode, +{ pub fn create_request_no_params(&self, json_rpc_id: String) -> JsonRpcRequest { self.create_request(NoParams::default(), json_rpc_id) } } -impl<'a, I, O, E> std::convert::From<&JsonRpcMethod> for String { +impl<'a, I, O, E> std::convert::From<&JsonRpcMethod> for String +where + E: MapErrorCode, +{ fn from(value: &JsonRpcMethod) -> Self { return value.method.clone().into(); } @@ -64,7 +78,7 @@ impl<'a, I, O, E> std::convert::From<&JsonRpcMethod> for String { impl<'de, I, O, E> JsonRpcMethod where O: Deserialize<'de>, - E: Deserialize<'de>, + E: Deserialize<'de> + MapErrorCode, { pub fn parse_json_response_str( &self, @@ -77,7 +91,7 @@ where impl JsonRpcMethod where O: DeserializeOwned, - E: DeserializeOwned, + E: DeserializeOwned + MapErrorCode, { pub fn parse_json_response_value( &self, @@ -119,7 +133,10 @@ impl Serialize for NoParams { } impl JsonRpcRequest { - pub fn new(method: JsonRpcMethod, params: I) -> Self { + pub fn new(method: JsonRpcMethod, params: I) -> Self + where + E: MapErrorCode, + { return Self { jsonrpc: String::from("2.0"), id: generate_random_rpc_id(), @@ -130,7 +147,10 @@ impl JsonRpcRequest { } impl JsonRpcRequest { - pub fn new_no_params(method: JsonRpcMethod) -> Self { + pub fn new_no_params(method: JsonRpcMethod) -> Self + where + E: MapErrorCode, + { return Self { jsonrpc: String::from("2.0"), id: generate_random_rpc_id(), @@ -168,6 +188,28 @@ pub struct ErrorData { pub data: Option, } +impl ErrorData +where + E: MapErrorCode, +{ + pub fn code_str(&self) -> &str { + return E::get_code_str(self.code); + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DefaultError; + +pub trait MapErrorCode { + fn get_code_str(code: i64) -> &'static str; +} + +impl MapErrorCode for DefaultError { + fn get_code_str(code: i64) -> &'static str { + map_json_rpc_error_code_to_str(code) + } +} + #[cfg(test)] mod test { @@ -249,7 +291,7 @@ mod test { #[test] fn create_rpc_request_from_call() { - let rpc_method = JsonRpcMethod::::new("test.method"); + let rpc_method = JsonRpcMethod::::new("test.method"); let json_rpc_id = generate_random_rpc_id(); let rpc_request = rpc_method.create_request_no_params(json_rpc_id); @@ -260,7 +302,7 @@ mod test { #[test] fn parse_rpc_response_success_from_call() { - let rpc_method = JsonRpcMethod::::new("test.return_string"); + let rpc_method = JsonRpcMethod::::new("test.return_string"); let json_value = serde_json::json!({ "jsonrpc" : "2.0", @@ -284,8 +326,7 @@ mod test { #[test] fn parse_rpc_response_failure_from_call() { - let rpc_method: JsonRpcMethod = - JsonRpcMethod::::new("test.return_string"); + let rpc_method = JsonRpcMethod::::new("test.return_string"); let json_value = serde_json::json!({ "jsonrpc" : "2.0", @@ -302,9 +343,10 @@ mod test { assert_eq!(err.jsonrpc, "2.0"); assert_eq!(err.error.code, -32700); - assert_eq!(err.error.message, "Failed to parse response"); + assert_eq!(err.error.code_str(), "parsing_error"); - assert_eq!(err.id, "request_id") + assert_eq!(err.error.message, "Failed to parse response"); + assert_eq!(err.id, "request_id"); } JsonRpcResponse::Ok(_ok) => { panic!("Failure deserialized as Ok") diff --git a/libs/gl-client/src/lsps/json_rpc_erased.rs b/libs/gl-client/src/lsps/json_rpc_erased.rs index 1f3dd6432..3f588361e 100644 --- a/libs/gl-client/src/lsps/json_rpc_erased.rs +++ b/libs/gl-client/src/lsps/json_rpc_erased.rs @@ -20,7 +20,7 @@ use crate::lsps::json_rpc::{ ErrorData, JsonRpcMethod, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseFailure, - JsonRpcResponseSuccess, + JsonRpcResponseSuccess, MapErrorCode, }; use serde::Serialize; @@ -54,7 +54,7 @@ impl JsonRpcMethodErased for JsonRpcMethod where I: serde::de::DeserializeOwned + Serialize, O: serde::de::DeserializeOwned + Serialize, - E: serde::de::DeserializeOwned + Serialize, + E: serde::de::DeserializeOwned + Serialize + MapErrorCode, { fn name(&self) -> &str { &self.method @@ -95,7 +95,7 @@ impl JsonRpcMethod where I: serde::de::DeserializeOwned + Serialize + 'static, O: serde::de::DeserializeOwned + Serialize + 'static, - E: serde::de::DeserializeOwned + Serialize + 'static, + E: serde::de::DeserializeOwned + Serialize + 'static + MapErrorCode, { pub fn erase_box(self) -> Box { return Box::new(self); @@ -143,7 +143,7 @@ pub trait JsonRpcMethodUnerased<'a, I, O, E> { impl<'a, I, O, E> JsonRpcMethodUnerased<'a, I, O, E> for JsonRpcMethod where O: serde::de::DeserializeOwned, - E: serde::de::DeserializeOwned, + E: serde::de::DeserializeOwned + MapErrorCode, { fn name(&self) -> &str { JsonRpcMethod::name(self) @@ -292,7 +292,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::lsps::json_rpc::{generate_random_rpc_id, JsonRpcMethod}; + use crate::lsps::json_rpc::{generate_random_rpc_id, DefaultError, JsonRpcMethod}; #[derive(Serialize, serde::Deserialize)] struct TestRequestStruct { @@ -306,7 +306,7 @@ mod test { #[test] fn create_rpc_request_from_method_erased() { - let rpc_method = JsonRpcMethod::::new("test.method"); + let rpc_method = JsonRpcMethod::::new("test.method"); let rpc_method_erased = rpc_method.erase_box(); // This rpc-request should work becasue the parameters match the schema @@ -322,7 +322,7 @@ mod test { #[test] fn create_rpc_request_from_method_erased_checks_types() { - let rpc_method = JsonRpcMethod::::new("test.method"); + let rpc_method = JsonRpcMethod::::new("test.method"); let rpc_method_erased = rpc_method.erase_box(); // This rpc-request should fail because the parameters do not match the schema @@ -335,8 +335,9 @@ mod test { #[test] fn parse_rpc_request_from_method_erased() { - let rpc_method = - JsonRpcMethod::::new("test.method"); + let rpc_method = JsonRpcMethod::::new( + "test.method", + ); let rpc_method_erased = rpc_method.erase_box(); let json_value = serde_json::json!({ @@ -352,8 +353,9 @@ mod test { #[test] fn parse_rpc_request_from_method_erased_fails() { - let rpc_method = - JsonRpcMethod::::new("test.method"); + let rpc_method = JsonRpcMethod::::new( + "test.method", + ); let rpc_method_erased = rpc_method.erase_box(); let json_value = serde_json::json!({ diff --git a/libs/gl-client/src/lsps/message.rs b/libs/gl-client/src/lsps/message.rs index 49708aca0..2d747b12b 100644 --- a/libs/gl-client/src/lsps/message.rs +++ b/libs/gl-client/src/lsps/message.rs @@ -1,6 +1,6 @@ // TODO: Implement parsing of error types for lsps2.getinfo use crate::lsps::error::LspsError; -pub use crate::lsps::json_rpc::{JsonRpcMethod, NoParams}; +pub use crate::lsps::json_rpc::{DefaultError, JsonRpcMethod, MapErrorCode, NoParams}; use crate::lsps::json_rpc_erased::{JsonRpcMethodErased, JsonRpcMethodUnerased}; use serde::de::Error as DeError; use serde::ser::Error as SeError; @@ -11,6 +11,8 @@ use time::macros::format_description; use time::{OffsetDateTime, PrimitiveDateTime}; use uuid::Uuid; +use super::error::map_json_rpc_error_code_to_str; + type OnchainFeeRate = u64; // All rpc-methods defined in the LSPS standard @@ -23,13 +25,12 @@ type OnchainFeeRate = u64; // 1. Add it to the JsonRpcMethodEnum // 2. Add it to the from_method_name function // 3. Add it to the ref_erase function -pub type Lsps0ListProtocols = JsonRpcMethod; -pub type Lsps1Info = JsonRpcMethod; -pub type Lsps1Order = JsonRpcMethod; -pub type Lsps2GetVersions = JsonRpcMethod; -pub type Lsps2GetInfo = JsonRpcMethod; -pub type Lsps2Buy = JsonRpcMethod; - +pub type Lsps0ListProtocols = JsonRpcMethod; +pub type Lsps1Info = JsonRpcMethod; +pub type Lsps1Order = JsonRpcMethod; +pub type Lsps2GetVersions = JsonRpcMethod; +pub type Lsps2GetInfo = JsonRpcMethod; +pub type Lsps2Buy = JsonRpcMethod; pub const LSPS0_LIST_PROTOCOLS: Lsps0ListProtocols = Lsps0ListProtocols::new("lsps0.list_protocols"); @@ -90,7 +91,7 @@ pub enum JsonRpcMethodEnum { Lsps1Order(Lsps1Order), Lsp2GetVersions(Lsps2GetVersions), Lsps2GetInfo(Lsps2GetInfo), - Lsps2Buy(Lsps2Buy) + Lsps2Buy(Lsps2Buy), } impl JsonRpcMethodEnum { @@ -115,7 +116,7 @@ impl JsonRpcMethodEnum { Self::Lsps1Order(order) => order.ref_erase(), Self::Lsp2GetVersions(order) => order.ref_erase(), Self::Lsps2GetInfo(order) => order.ref_erase(), - Self::Lsps2Buy(buy) => buy.ref_erase() + Self::Lsps2Buy(buy) => buy.ref_erase(), } } } @@ -223,7 +224,6 @@ impl<'de> Deserialize<'de> for MsatAmount { // achieved if the LSPS2 sends a fully compliant timestamp. // // I have decided to fail early if another timestamp is received - #[derive(Debug)] pub struct Datetime { datetime: PrimitiveDateTime, @@ -394,6 +394,19 @@ pub struct Lsp2GetInfoResponse { max_payment_size_msat: String, } +#[derive(Debug, Serialize, Deserialize)] +pub struct Lsp2GetInfoError {} + +impl MapErrorCode for Lsp2GetInfoError { + fn get_code_str(code: i64) -> &'static str { + match code { + 1 => "unsupported_version", + 2 => "unrecognized_or_stale_token", + _ => map_json_rpc_error_code_to_str(code), + } + } +} + #[derive(Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct OpeningFeeParamsMenuItem { @@ -414,10 +427,25 @@ pub struct Lsps2BuyRequest { #[derive(Debug, Serialize, Deserialize)] pub struct Lsps2BuyResponse { - jit_channel_scid : String, - lsp_cltv_expiry_delta : u64, + jit_channel_scid: String, + lsp_cltv_expiry_delta: u64, #[serde(default)] - client_trusts_lsp : bool + client_trusts_lsp: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Lsps2BuyError {} + +impl MapErrorCode for Lsps2BuyError { + fn get_code_str(code: i64) -> &'static str { + match code { + 1 => "unsupported_version", + 2 => "invalid_opening_fee_params", + 3 => "payment_size_too_small", + 4 => "payment_size_too_large", + _ => map_json_rpc_error_code_to_str(code), + } + } } #[cfg(test)] @@ -461,8 +489,6 @@ mod test { fn parsing_error_when_opening_fee_menu_has_extra_fields() { // LSPS2 mentions // Clients MUST fail and abort the flow if a opening_fee_params object has unrecognized fields. - // - // If a new field is added the version number should be incremented let fee_menu_item = serde_json::json!( { "min_fee_msat": "546000", @@ -545,6 +571,9 @@ mod test { #[test] fn parse_too_long_promise_fails() { // Each a char correspond to 1 byte + // We refuse to parse the promise if it is too long + // LSPS2 requires us to ignore Promise that are too long + // so the client cannot be burdened with unneeded storage requirements let a_513_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; let a_512_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; @@ -560,9 +589,12 @@ mod test { "lsp_cltv_expiry_delta" : 144 }); - let buy_response = serde_json::from_value::(data).expect("The response can be parsed"); - - assert!(! buy_response.client_trusts_lsp, "If the field is absent it assumed the client should not trust the LSP") + let buy_response = + serde_json::from_value::(data).expect("The response can be parsed"); + assert!( + !buy_response.client_trusts_lsp, + "If the field is absent it assumed the client should not trust the LSP" + ) } } diff --git a/libs/gl-client/src/lsps/mod.rs b/libs/gl-client/src/lsps/mod.rs index 8e2b78f76..7ba871b1b 100644 --- a/libs/gl-client/src/lsps/mod.rs +++ b/libs/gl-client/src/lsps/mod.rs @@ -1,3 +1,4 @@ +// NOTE: Both the LSP-spec and this implementation are still moving heavily pub mod error; pub mod json_rpc; pub mod json_rpc_erased;