lsp_primitives/lsps2/
schema.rs

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