Skip to main content

fedimint_lnv2_common/
config.rs

1use std::collections::BTreeMap;
2
3pub use bitcoin::Network;
4use fedimint_core::config::ExcessiveRelativeFeeError;
5use fedimint_core::core::ModuleKind;
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_core::envs::BitcoinRpcConfig;
8use fedimint_core::{Amount, PeerId, plugin_types_trait_impl_config};
9use group::Curve;
10use serde::{Deserialize, Serialize};
11use tpe::{AggregatePublicKey, PublicKeyShare, SecretKeyShare};
12
13use crate::LightningCommonInit;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct LightningConfig {
17    pub private: LightningConfigPrivate,
18    pub consensus: LightningConfigConsensus,
19}
20
21#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
22pub struct LightningConfigLocal {
23    pub bitcoin_rpc: BitcoinRpcConfig,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable)]
27pub struct LightningConfigConsensus {
28    pub tpe_agg_pk: AggregatePublicKey,
29    pub tpe_pks: BTreeMap<PeerId, PublicKeyShare>,
30    pub fee_consensus: FeeConsensus,
31    pub network: Network,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct LightningConfigPrivate {
36    pub sk: SecretKeyShare,
37}
38
39#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
40pub struct LightningClientConfig {
41    pub tpe_agg_pk: AggregatePublicKey,
42    pub tpe_pks: BTreeMap<PeerId, PublicKeyShare>,
43    pub fee_consensus: FeeConsensus,
44    pub network: Network,
45}
46
47impl std::fmt::Display for LightningClientConfig {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "LightningClientConfig {self:?}")
50    }
51}
52
53// Wire together the configs for this module
54plugin_types_trait_impl_config!(
55    LightningCommonInit,
56    LightningConfig,
57    LightningConfigPrivate,
58    LightningConfigConsensus,
59    LightningClientConfig
60);
61
62#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
63pub struct FeeConsensus {
64    pub base: Amount,
65    pub parts_per_million: u64,
66}
67
68impl FeeConsensus {
69    /// The lightning module will charge a non-configurable base fee of one
70    /// satoshi per transaction input and output to account for the costs
71    /// incurred by the federation for processing the transaction. On top of
72    /// that the federation may charge a additional relative fee per input and
73    /// output of up to one thousand parts per million which is equal to one
74    /// tenth of one percent.
75    ///
76    /// # Errors
77    /// - Returns [`ExcessiveRelativeFeeError`] if the relative fee is in excess
78    ///   of one thousand parts per million.
79    pub fn new(parts_per_million: u64) -> Result<Self, ExcessiveRelativeFeeError> {
80        const MAX_PARTS_PER_MILLION: u64 = 1_000;
81
82        if parts_per_million > MAX_PARTS_PER_MILLION {
83            return Err(ExcessiveRelativeFeeError {
84                parts_per_million,
85                max: MAX_PARTS_PER_MILLION,
86            });
87        }
88
89        Ok(Self {
90            base: Amount::from_sats(1),
91            parts_per_million,
92        })
93    }
94
95    pub fn zero() -> Self {
96        Self {
97            base: Amount::ZERO,
98            parts_per_million: 0,
99        }
100    }
101
102    pub fn fee(&self, amount: Amount) -> Amount {
103        Amount::from_msats(self.fee_msats(amount.msats))
104    }
105
106    fn fee_msats(&self, msats: u64) -> u64 {
107        msats
108            .saturating_mul(self.parts_per_million)
109            .saturating_div(1_000_000)
110            .checked_add(self.base.msats)
111            .expect("The division creates sufficient headroom to add the base fee")
112    }
113}
114
115#[test]
116fn test_fee_consensus() {
117    let fee_consensus = FeeConsensus::new(1_000).expect("Relative fee is within range");
118
119    assert_eq!(
120        fee_consensus.fee(Amount::from_msats(999)),
121        Amount::from_sats(1)
122    );
123
124    assert_eq!(
125        fee_consensus.fee(Amount::from_sats(1)),
126        Amount::from_msats(1) + Amount::from_sats(1)
127    );
128
129    assert_eq!(
130        fee_consensus.fee(Amount::from_sats(1000)),
131        Amount::from_sats(1) + Amount::from_sats(1)
132    );
133
134    assert_eq!(
135        fee_consensus.fee(Amount::from_bitcoins(1)),
136        Amount::from_sats(100_000) + Amount::from_sats(1)
137    );
138
139    assert_eq!(
140        fee_consensus.fee(Amount::from_bitcoins(100_000)),
141        Amount::from_bitcoins(100) + Amount::from_sats(1)
142    );
143}
144
145#[test]
146fn a_relative_fee_over_the_limit_is_rejected() {
147    let err: ExcessiveRelativeFeeError =
148        FeeConsensus::new(1_001).expect_err("A fee over one per mille is excessive");
149
150    assert_eq!(err.parts_per_million, 1_001);
151    assert_eq!(err.max, 1_000);
152}
153
154#[allow(dead_code)]
155fn migrate_config_consensus(
156    config: &fedimint_ln_common::config::LightningConfigConsensus,
157    peer_count: u16,
158) -> LightningConfigConsensus {
159    LightningConfigConsensus {
160        tpe_agg_pk: AggregatePublicKey(config.threshold_pub_keys.public_key().0.to_affine()),
161        tpe_pks: (0..peer_count)
162            .map(|peer| {
163                (
164                    PeerId::from(peer),
165                    PublicKeyShare(
166                        config
167                            .threshold_pub_keys
168                            .public_key_share(peer as usize)
169                            .0
170                            .0
171                            .to_affine(),
172                    ),
173                )
174            })
175            .collect(),
176        fee_consensus: FeeConsensus::new(1000).expect("Relative fee is within range"),
177        network: config.network.0,
178    }
179}
180
181#[allow(dead_code)]
182fn migrate_config_private(
183    config: &fedimint_ln_common::config::LightningConfigPrivate,
184) -> LightningConfigPrivate {
185    LightningConfigPrivate {
186        sk: SecretKeyShare(config.threshold_sec_key.0.0.0),
187    }
188}
189
190#[allow(dead_code)]
191fn migrate_config_local(
192    config: fedimint_ln_common::config::LightningConfigLocal,
193) -> LightningConfigLocal {
194    LightningConfigLocal {
195        bitcoin_rpc: config.bitcoin_rpc,
196    }
197}