lsp_primitives/lsps1/
util.rs

1use crate::json_rpc::ErrorData;
2use crate::lsps1::schema::{Lsps1CreateOrderRequest, Lsps1Options};
3use anyhow::Result;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Serialize, Deserialize, Debug, Clone)]
8pub struct Lsps1OptionMismatchError {
9    property: String,
10    message: String,
11}
12
13impl From<Lsps1OptionMismatchError> for ErrorData {
14    fn from(options_error: Lsps1OptionMismatchError) -> Self {
15        match serde_json::to_value(options_error) {
16            Ok(data) => ErrorData {
17                code: 1000,
18                message: "Option mismatch".to_string(),
19                data: Some(data),
20            },
21            Err(e) => ErrorData::internal_error(serde_json::Value::String(format!("{:?}", e))),
22        }
23    }
24}
25
26impl Lsps1OptionMismatchError {
27    fn new(property: String, message: String) -> Self {
28        Self { property, message }
29    }
30}
31
32impl Lsps1CreateOrderRequest {
33    pub fn validate_options(&self, options: &Lsps1Options) -> Result<(), Lsps1OptionMismatchError> {
34        if self.client_balance_sat < options.min_initial_client_balance_sat {
35            return Err(Lsps1OptionMismatchError::new(
36                "min_initial_client_balance_sat".to_string(), 
37                format!("You've requested client_balance_sat={} but the LSP-server requires at least {}",
38                        self.client_balance_sat,
39                        options.min_initial_client_balance_sat)));
40        }
41
42        if self.client_balance_sat > options.max_initial_client_balance_sat {
43            return Err(Lsps1OptionMismatchError::new(
44                "max_initial_client_balance_sat".to_string(), 
45                format!("You've requested client_balance_sat={} but the LSP-server doesn't allow this value to exceed {}",
46                        self.client_balance_sat,
47                        options.max_initial_client_balance_sat)));
48        }
49
50        if self.lsp_balance_sat < options.min_initial_lsp_balance_sat {
51            return Err(Lsps1OptionMismatchError::new(
52                    "min_initial_lsp_balance_sat".to_string(),
53                    format!("You've requested a channel with lsp_balance_sat={} but the LSP-server requires at least {}",
54                            self.lsp_balance_sat,
55                            options.min_initial_lsp_balance_sat)));
56        }
57
58        if self.lsp_balance_sat > options.max_initial_lsp_balance_sat {
59            return Err(Lsps1OptionMismatchError::new(
60                    "max_initial_lsp_balance_sat".to_string(),
61                    format!("You've requested a channel with lsp_balance_sat={} but the LSP-server doesn't allow this value to exceed {}",
62                            self.lsp_balance_sat,
63                            options.max_initial_lsp_balance_sat)));
64        }
65
66        // Compute the capacity of the channel and validate it
67        // The checked_add returns None on overflow
68        let capacity_option = self.lsp_balance_sat.checked_add(&self.client_balance_sat);
69        let capacity = match capacity_option {
70            Some(c) => c,
71            None => {
72                return Err(Lsps1OptionMismatchError::new(
73                    "max_channel_balance_sat".to_string(),
74                    "Overflow when computing channel_capacity".to_string(),
75                ));
76            }
77        };
78
79        if capacity < options.min_channel_balance_sat {
80            return Err(Lsps1OptionMismatchError::new(
81                    "min_channel_balance_sat".to_string(),
82                    format!("You've requested a channel with capacity={} but the LSP-server requires at least {}", 
83                            capacity,
84                            options.min_channel_balance_sat
85                    )));
86        };
87
88        if capacity > options.max_channel_balance_sat {
89            return Err(Lsps1OptionMismatchError::new(
90                    "max_channel_balance_sat".to_string(),
91                    format!("You've requested a channel with capacity={} but the LSP-server only allows values up to {}",
92                            capacity,
93                            options.max_channel_balance_sat
94                            )));
95        }
96
97        // Verify the channel_expiry_blocks
98        if self.channel_expiry_blocks > options.max_channel_expiry_blocks {
99            return Err(Lsps1OptionMismatchError::new(
100                    "max_channel_expiry_blocks".to_string(),
101                    format!("You've requested to lease a channel for channel_expiry_block={} but the LSP-server only allows max_channel_expiry_blocks={}",
102                            self.channel_expiry_blocks,
103                            options.max_channel_expiry_blocks
104                            )));
105        }
106        Ok(())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112
113    use crate::lsps0::common_schemas::SatAmount;
114    use crate::lsps1::builders::{Lsps1CreateOrderRequestBuilder, Lsps1OptionsBuilder};
115
116    fn get_options_builder() -> Lsps1OptionsBuilder {
117        Lsps1OptionsBuilder::new()
118            .minimum_channel_confirmations(0)
119            .minimum_onchain_payment_confirmations(None)
120            .supports_zero_channel_reserve(true)
121            .min_onchain_payment_size_sat(None)
122            .max_channel_expiry_blocks(1_000)
123            .min_initial_client_balance_sat(SatAmount::new(0))
124            .max_initial_client_balance_sat(SatAmount::new(0))
125            .min_initial_lsp_balance_sat(SatAmount::new(100_000))
126            .max_initial_lsp_balance_sat(SatAmount::new(100_000_000))
127            .min_channel_balance_sat(SatAmount::new(100_000))
128            .max_channel_balance_sat(SatAmount::new(100_000_000))
129    }
130
131    fn get_order_builder() -> Lsps1CreateOrderRequestBuilder {
132        Lsps1CreateOrderRequestBuilder::new()
133            .client_balance_sat(Some(SatAmount::new(0)))
134            .lsp_balance_sat(SatAmount::new(500_000))
135            .confirms_within_blocks(Some(6))
136            .channel_expiry_blocks(1_000)
137            .token(None)
138            .refund_onchain_address(None)
139            .announce_channel(Some(false))
140    }
141
142    #[test]
143    fn test_validate_order_against_options_okay() {
144        let options = get_options_builder().build().unwrap();
145        let order = get_order_builder().build().unwrap();
146
147        // Should be valid
148        order.validate_options(&options).unwrap();
149    }
150
151    #[test]
152    fn test_validate_order_against_options_lsp_balance_sat_error() {
153        let options = get_options_builder()
154            .min_initial_lsp_balance_sat(SatAmount::new(1_000))
155            .max_initial_lsp_balance_sat(SatAmount::new(100_000))
156            .build()
157            .unwrap();
158        let order_1 = get_order_builder()
159            .lsp_balance_sat(SatAmount::new(0))
160            .build()
161            .unwrap();
162
163        let order_2 = get_order_builder()
164            .lsp_balance_sat(SatAmount::new(999_999))
165            .build()
166            .unwrap();
167
168        let err1 = order_1.validate_options(&options).unwrap_err();
169        let err2 = order_2.validate_options(&options).unwrap_err();
170
171        assert_eq!(err1.property, "min_initial_lsp_balance_sat");
172        assert_eq!(err2.property, "max_initial_lsp_balance_sat");
173    }
174
175    #[test]
176    fn test_validate_order_against_options_client_balance_sat_error() {
177        let options = get_options_builder()
178            .min_initial_client_balance_sat(SatAmount::new(1_000))
179            .max_initial_client_balance_sat(SatAmount::new(100_000))
180            .build()
181            .unwrap();
182
183        let order_1 = get_order_builder()
184            .client_balance_sat(Some(SatAmount::new(0)))
185            .build()
186            .unwrap();
187
188        let order_2 = get_order_builder()
189            .client_balance_sat(Some(SatAmount::new(999_999)))
190            .build()
191            .unwrap();
192
193        let err1 = order_1.validate_options(&options).unwrap_err();
194        let err2 = order_2.validate_options(&options).unwrap_err();
195
196        assert_eq!(err1.property, "min_initial_client_balance_sat");
197        assert_eq!(err2.property, "max_initial_client_balance_sat");
198    }
199
200    #[test]
201    fn test_validate_order_against_capacity_from_options_error() {
202        let options = get_options_builder()
203            .min_initial_client_balance_sat(SatAmount::new(1_000))
204            .max_initial_client_balance_sat(SatAmount::new(100_000))
205            .min_initial_lsp_balance_sat(SatAmount::new(1_000))
206            .max_initial_lsp_balance_sat(SatAmount::new(100_000))
207            .min_channel_balance_sat(SatAmount::new(10_000))
208            .max_channel_balance_sat(SatAmount::new(100_000))
209            .build()
210            .unwrap();
211
212        let order_1 = get_order_builder()
213            .client_balance_sat(Some(SatAmount::new(1_000)))
214            .lsp_balance_sat(SatAmount::new(1_000))
215            .build()
216            .unwrap();
217
218        let order_2 = get_order_builder()
219            .client_balance_sat(Some(SatAmount::new(100_000)))
220            .lsp_balance_sat(SatAmount::new(100_000))
221            .build()
222            .unwrap();
223
224        let err1 = order_1.validate_options(&options).unwrap_err();
225        let err2 = order_2.validate_options(&options).unwrap_err();
226
227        assert_eq!(err1.property, "min_channel_balance_sat");
228        assert_eq!(err2.property, "max_channel_balance_sat");
229    }
230}