bs_gl_client/lsps/lsps2/
schema.rs

1use serde::de::Error as DeError;
2use serde::{Deserialize, Serialize};
3
4use crate::lsps::error::map_json_rpc_error_code_to_str;
5use crate::lsps::json_rpc::MapErrorCode;
6use crate::lsps::lsps0::common_schemas::{IsoDatetime, MsatAmount, ShortChannelId};
7
8const MAX_PROMISE_LEN_BYTES: usize = 512;
9#[derive(Debug)]
10struct Promise {
11    promise: String,
12}
13
14impl Promise {
15    fn new(promise: String) -> Result<Self, String> {
16        if promise.len() <= MAX_PROMISE_LEN_BYTES {
17            Ok(Promise { promise })
18        } else {
19            Err(String::from("Promise exceeds maximum length"))
20        }
21    }
22}
23
24impl Serialize for Promise {
25    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26    where
27        S: serde::Serializer,
28    {
29        serializer.serialize_str(&self.promise)
30    }
31}
32
33// We only accept promises with a max length of 512 to be compliant to the spec
34// Note, that serde-json still forces us to parse the entire string fully.
35// However, the client will not story the overly large json-file
36impl<'de> Deserialize<'de> for Promise {
37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38    where
39        D: serde::Deserializer<'de>,
40    {
41        let str_repr = String::deserialize(deserializer)?;
42        let promise = Promise::new(str_repr.clone());
43        format!("{:?}", &promise);
44        Promise::new(str_repr.clone())
45            .map_err(|_| D::Error::custom("promise exceeds max length"))
46    }
47}
48
49#[derive(Debug, Serialize, Deserialize)]
50pub struct Lsps2GetVersionsResponse {
51    versions: Vec<u64>,
52}
53
54#[derive(Debug, Serialize, Deserialize)]
55pub struct Lsps2GetInfoRequest {
56    version: i64,
57    token: Option<String>,
58}
59
60#[derive(Debug, Serialize, Deserialize)]
61pub struct Lsps2GetInfoResponse {
62    opening_fee_params_menu: Vec<OpeningFeeParamsMenuItem>,
63    min_payment_size_msat: String,
64    max_payment_size_msat: String,
65}
66
67#[derive(Debug, Serialize, Deserialize)]
68pub struct Lsps2GetInfoError {}
69
70impl MapErrorCode for Lsps2GetInfoError {
71    fn get_code_str(code: i64) -> &'static str {
72        match code {
73            1 => "unsupported_version",
74            2 => "unrecognized_or_stale_token",
75            _ => map_json_rpc_error_code_to_str(code),
76        }
77    }
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81#[serde(deny_unknown_fields)]
82pub struct OpeningFeeParamsMenuItem {
83    min_fee_msat: MsatAmount,
84    proportional: u64,
85    valid_until: IsoDatetime,
86    min_lifetime: u64,
87    max_client_to_self_delay: u64,
88    promise: Promise,
89}
90
91#[derive(Debug, Serialize, Deserialize)]
92pub struct Lsps2BuyRequest {
93    version: String,
94    opening_fee_params: OpeningFeeParamsMenuItem,
95    payment_size_msat: MsatAmount,
96}
97
98#[derive(Debug, Serialize, Deserialize)]
99pub struct Lsps2BuyResponse {
100    jit_channel_scid: ShortChannelId,
101    lsp_cltv_expiry_delta: u64,
102    #[serde(default)]
103    client_trusts_lsp: bool,
104}
105
106#[derive(Debug, Serialize, Deserialize)]
107pub struct Lsps2BuyError {}
108
109impl MapErrorCode for Lsps2BuyError {
110    fn get_code_str(code: i64) -> &'static str {
111        match code {
112            1 => "unsupported_version",
113            2 => "invalid_opening_fee_params",
114            3 => "payment_size_too_small",
115            4 => "payment_size_too_large",
116            _ => map_json_rpc_error_code_to_str(code),
117        }
118    }
119}
120
121#[cfg(test)]
122mod test {
123    use super::*;
124
125    #[test]
126    fn parsing_error_when_opening_fee_menu_has_extra_fields() {
127        // LSPS2 mentions
128        // Clients MUST fail and abort the flow if a opening_fee_params object has unrecognized fields.
129        let fee_menu_item = serde_json::json!(
130            {
131                "min_fee_msat": "546000",
132                "proportional": 1200,
133                "valid_until": "2023-02-23T08:47:30.511Z",
134                "min_lifetime": 1008,
135                "max_client_to_self_delay": 2016,
136                "promise": "abcdefghijklmnopqrstuvwxyz",
137                "extra_field" : "This shouldn't be their"
138            }
139        );
140
141        let parsed_opening_fee_menu_item: Result<OpeningFeeParamsMenuItem, _> =
142            serde_json::from_value(fee_menu_item);
143        assert!(
144            parsed_opening_fee_menu_item.is_err_and(|x| format!("{}", x).contains("extra_field"))
145        )
146    }
147
148    #[test]
149    fn parse_valid_promise() {
150        let promise_json = "\"abcdefghijklmnopqrstuvwxyz\"";
151        let promise = serde_json::from_str::<Promise>(promise_json).expect("Can parse promise");
152        assert_eq!(promise.promise, "abcdefghijklmnopqrstuvwxyz");
153    }
154
155    #[test]
156    fn parse_too_long_promise_fails() {
157        // Each a char correspond to 1 byte
158        // We refuse to parse the promise if it is too long
159        // LSPS2 requires us to ignore Promise that are too long
160        // so the client cannot be burdened with unneeded storage requirements
161        let a_513_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"";
162        let a_512_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"";
163
164        serde_json::from_str::<Promise>(a_512_chars)
165            .expect("Should fail because 512 bytes is the max length");
166        serde_json::from_str::<Promise>(a_513_chars).expect_err("Should fail to parse promise ");
167    }
168
169    #[test]
170    fn client_trust_lsp_defaults_to_false() {
171        let data = serde_json::json!({
172            "jit_channel_scid" : "0x12x12",
173            "lsp_cltv_expiry_delta" : 144
174        });
175
176        let buy_response =
177            serde_json::from_value::<Lsps2BuyResponse>(data).expect("The response can be parsed");
178
179        assert!(
180            !buy_response.client_trusts_lsp,
181            "If the field is absent it assumed the client should not trust the LSP"
182        )
183    }
184}