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        Promise::new(str_repr.clone()).map_err(|_| D::Error::custom("promise exceeds max length"))
43    }
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47pub struct Lsps2GetVersionsResponse {
48    versions: Vec<u64>,
49}
50
51#[derive(Debug, Serialize, Deserialize)]
52pub struct Lsps2GetInfoRequest {
53    version: i64,
54    token: Option<String>,
55}
56
57#[derive(Debug, Serialize, Deserialize)]
58pub struct Lsps2GetInfoResponse {
59    opening_fee_params_menu: Vec<OpeningFeeParamsMenuItem>,
60    min_payment_size_msat: String,
61    max_payment_size_msat: String,
62}
63
64#[derive(Debug, Serialize, Deserialize)]
65pub struct Lsps2GetInfoError {}
66
67impl MapErrorCode for Lsps2GetInfoError {
68    fn get_code_str(code: i64) -> &'static str {
69        match code {
70            1 => "unsupported_version",
71            2 => "unrecognized_or_stale_token",
72            _ => map_json_rpc_error_code_to_str(code),
73        }
74    }
75}
76
77#[derive(Debug, Serialize, Deserialize)]
78#[serde(deny_unknown_fields)]
79pub struct OpeningFeeParamsMenuItem {
80    min_fee_msat: MsatAmount,
81    proportional: u64,
82    valid_until: IsoDatetime,
83    min_lifetime: u64,
84    max_client_to_self_delay: u64,
85    promise: Promise,
86}
87
88#[derive(Debug, Serialize, Deserialize)]
89pub struct Lsps2BuyRequest {
90    version: String,
91    opening_fee_params: OpeningFeeParamsMenuItem,
92    payment_size_msat: MsatAmount,
93}
94
95#[derive(Debug, Serialize, Deserialize)]
96pub struct Lsps2BuyResponse {
97    jit_channel_scid: ShortChannelId,
98    lsp_cltv_expiry_delta: u64,
99    #[serde(default)]
100    client_trusts_lsp: bool,
101}
102
103#[derive(Debug, Serialize, Deserialize)]
104pub struct Lsps2BuyError {}
105
106impl MapErrorCode for Lsps2BuyError {
107    fn get_code_str(code: i64) -> &'static str {
108        match code {
109            1 => "unsupported_version",
110            2 => "invalid_opening_fee_params",
111            3 => "payment_size_too_small",
112            4 => "payment_size_too_large",
113            _ => map_json_rpc_error_code_to_str(code),
114        }
115    }
116}
117
118#[cfg(test)]
119mod test {
120    use super::*;
121
122    #[test]
123    fn parsing_error_when_opening_fee_menu_has_extra_fields() {
124        // LSPS2 mentions
125        // Clients MUST fail and abort the flow if a opening_fee_params object has unrecognized fields.
126        let fee_menu_item = serde_json::json!(
127            {
128                "min_fee_msat": "546000",
129                "proportional": 1200,
130                "valid_until": "2023-02-23T08:47:30.511Z",
131                "min_lifetime": 1008,
132                "max_client_to_self_delay": 2016,
133                "promise": "abcdefghijklmnopqrstuvwxyz",
134                "extra_field" : "This shouldn't be their"
135            }
136        );
137
138        let parsed_opening_fee_menu_item: Result<OpeningFeeParamsMenuItem, _> =
139            serde_json::from_value(fee_menu_item);
140        assert!(
141            parsed_opening_fee_menu_item.is_err_and(|x| format!("{}", x).contains("extra_field"))
142        )
143    }
144
145    #[test]
146    fn parse_valid_promise() {
147        let promise_json = "\"abcdefghijklmnopqrstuvwxyz\"";
148        let promise = serde_json::from_str::<Promise>(promise_json).expect("Can parse promise");
149        assert_eq!(promise.promise, "abcdefghijklmnopqrstuvwxyz");
150    }
151
152    #[test]
153    fn parse_too_long_promise_fails() {
154        // Each a char correspond to 1 byte
155        // We refuse to parse the promise if it is too long
156        // LSPS2 requires us to ignore Promise that are too long
157        // so the client cannot be burdened with unneeded storage requirements
158        let a_513_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"";
159        let a_512_chars = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"";
160
161        serde_json::from_str::<Promise>(a_512_chars)
162            .expect("Should fail because 512 bytes is the max length");
163        serde_json::from_str::<Promise>(a_513_chars).expect_err("Should fail to parse promise ");
164    }
165
166    #[test]
167    fn client_trust_lsp_defaults_to_false() {
168        let data = serde_json::json!({
169            "jit_channel_scid" : "0x12x12",
170            "lsp_cltv_expiry_delta" : 144
171        });
172
173        let buy_response =
174            serde_json::from_value::<Lsps2BuyResponse>(data).expect("The response can be parsed");
175
176        assert!(
177            !buy_response.client_trusts_lsp,
178            "If the field is absent it assumed the client should not trust the LSP"
179        )
180    }
181}