fedimint_ln_common/
config.rs

1use std::str::FromStr;
2
3use anyhow::Context;
4pub use bitcoin::Network;
5use fedimint_core::core::ModuleKind;
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_core::envs::BitcoinRpcConfig;
8use fedimint_core::{msats, plugin_types_trait_impl_config, Amount};
9use lightning_invoice::RoutingFees;
10use serde::{Deserialize, Serialize};
11use threshold_crypto::serde_impl::SerdeSecret;
12
13use crate::LightningCommonInit;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct LightningGenParams {
17    pub local: LightningGenParamsLocal,
18    pub consensus: LightningGenParamsConsensus,
19}
20
21impl LightningGenParams {
22    pub fn regtest(bitcoin_rpc: BitcoinRpcConfig) -> Self {
23        Self {
24            local: LightningGenParamsLocal { bitcoin_rpc },
25            consensus: LightningGenParamsConsensus {
26                network: Network::Regtest,
27            },
28        }
29    }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct LightningGenParamsConsensus {
34    pub network: Network,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct LightningGenParamsLocal {
39    pub bitcoin_rpc: BitcoinRpcConfig,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LightningConfig {
44    pub local: LightningConfigLocal,
45    pub private: LightningConfigPrivate,
46    pub consensus: LightningConfigConsensus,
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
50pub struct LightningConfigLocal {
51    /// Configures which bitcoin RPC to use
52    pub bitcoin_rpc: BitcoinRpcConfig,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable)]
56pub struct LightningConfigConsensus {
57    /// The threshold public keys for encrypting the LN preimage
58    pub threshold_pub_keys: threshold_crypto::PublicKeySet,
59    /// Fees charged for LN transactions
60    pub fee_consensus: FeeConsensus,
61    pub network: Network,
62}
63
64impl LightningConfigConsensus {
65    /// The number of decryption shares required
66    pub fn threshold(&self) -> usize {
67        self.threshold_pub_keys.threshold() + 1
68    }
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LightningConfigPrivate {
73    // TODO: propose serde(with = "…") based protection upstream instead
74    /// Our secret key for decrypting preimages
75    pub threshold_sec_key: SerdeSecret<threshold_crypto::SecretKeyShare>,
76}
77
78#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
79pub struct LightningClientConfig {
80    pub threshold_pub_key: threshold_crypto::PublicKey,
81    pub fee_consensus: FeeConsensus,
82    pub network: Network,
83}
84
85impl std::fmt::Display for LightningClientConfig {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        write!(
88            f,
89            "LightningClientConfig {}",
90            serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
91        )
92    }
93}
94
95// Wire together the configs for this module
96plugin_types_trait_impl_config!(
97    LightningCommonInit,
98    LightningGenParams,
99    LightningGenParamsLocal,
100    LightningGenParamsConsensus,
101    LightningConfig,
102    LightningConfigLocal,
103    LightningConfigPrivate,
104    LightningConfigConsensus,
105    LightningClientConfig
106);
107
108#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
109pub struct FeeConsensus {
110    pub contract_input: fedimint_core::Amount,
111    pub contract_output: fedimint_core::Amount,
112}
113
114impl Default for FeeConsensus {
115    fn default() -> Self {
116        Self {
117            contract_input: fedimint_core::Amount::ZERO,
118            contract_output: fedimint_core::Amount::ZERO,
119        }
120    }
121}
122
123/// Gateway routing fees
124#[derive(Debug, Clone)]
125pub struct GatewayFee(pub RoutingFees);
126
127impl FromStr for GatewayFee {
128    type Err = anyhow::Error;
129
130    fn from_str(s: &str) -> Result<Self, Self::Err> {
131        let routing_fees = parse_routing_fees(s)?;
132        Ok(GatewayFee(routing_fees))
133    }
134}
135
136pub fn parse_routing_fees(raw: &str) -> anyhow::Result<RoutingFees> {
137    let mut parts = raw.split(',');
138    let base_msat = parts
139        .next()
140        .context("missing base fee in millisatoshis")?
141        .parse()?;
142    let proportional_millionths = parts
143        .next()
144        .context("missing liquidity based fee as proportional millionths of routed amount")?
145        .parse()?;
146    Ok(RoutingFees {
147        base_msat,
148        proportional_millionths,
149    })
150}
151
152/// Trait for converting a fee type to specific `Amount`,
153/// relative to a given payment `Amount`
154pub trait FeeToAmount {
155    /// Calculates fee `Amount` given a payment `Amount`
156    fn to_amount(&self, payment: &Amount) -> Amount;
157}
158
159impl FeeToAmount for RoutingFees {
160    fn to_amount(&self, payment: &Amount) -> Amount {
161        let base_fee = u64::from(self.base_msat);
162        let margin_fee: u64 = if self.proportional_millionths > 0 {
163            let fee_percent = 1_000_000 / u64::from(self.proportional_millionths);
164            payment.msats / fee_percent
165        } else {
166            0
167        };
168
169        msats(base_fee + margin_fee)
170    }
171}
172
173impl FeeToAmount for GatewayFee {
174    fn to_amount(&self, payment: &Amount) -> Amount {
175        self.0.to_amount(payment)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use lightning_invoice::RoutingFees;
182
183    use super::parse_routing_fees;
184
185    #[test]
186    fn test_routing_fee_parsing() {
187        let test_cases = [
188            ("0,0", Some((0, 0))),
189            ("10,5000", Some((10, 5000))),
190            ("-10,5000", None),
191            ("10,-5000", None),
192            ("0;5000", None),
193            ("xpto", None),
194        ];
195        for (input, expected) in test_cases {
196            if let Some((base_msat, proportional_millionths)) = expected {
197                let actual = parse_routing_fees(input).expect("parsed routing fees");
198                assert_eq!(
199                    actual,
200                    RoutingFees {
201                        base_msat,
202                        proportional_millionths
203                    }
204                );
205            } else {
206                let result = parse_routing_fees(input);
207                assert!(result.is_err());
208            }
209        }
210    }
211}