Skip to main content

lexe_api_core/
cli.rs

1use std::{fmt, fmt::Display, path::Path, str::FromStr};
2
3use anyhow::Context;
4#[cfg(test)]
5use lexe_common::test_utils::arbitrary;
6use lexe_common::{
7    api::user::NodePk,
8    ln::{addr::LxSocketAddress, amount::Amount},
9};
10#[cfg(test)]
11use proptest_derive::Arbitrary;
12use rust_decimal::Decimal;
13use rust_decimal_macros::dec;
14use serde::{Deserialize, Serialize, de::DeserializeOwned};
15
16/// User node CLI args.
17pub mod node;
18
19/// A trait for the arguments to an enclave command.
20pub trait EnclaveArgs: Serialize + DeserializeOwned {
21    /// The name of the command, e.g. "run", "provision", "mega"
22    const NAME: &str;
23
24    /// Construct a [`std::process::Command`] from the contained args.
25    /// Requires the path to the binary.
26    fn to_command(&self, bin_path: &Path) -> std::process::Command {
27        let mut command = std::process::Command::new(bin_path);
28        self.append_args(&mut command);
29        command
30    }
31
32    /// Serialize and append the contained args to an existing
33    /// [`std::process::Command`].
34    fn append_args(&self, cmd: &mut std::process::Command) {
35        cmd.arg(Self::NAME).arg(self.to_json_string());
36    }
37
38    fn from_json_str(json_str: &str) -> Result<Self, serde_json::Error> {
39        serde_json::from_str(json_str)
40    }
41
42    fn to_json_string(&self) -> String {
43        serde_json::to_string(self).expect("JSON serialization failed")
44    }
45}
46
47/// Information about the LSP which the user node needs to connect and to
48/// generate route hints when no channel exists.
49#[cfg_attr(test, derive(Arbitrary))]
50#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
51pub struct LspInfo {
52    pub node_pk: NodePk,
53    /// The socket on which the LSP accepts P2P LN connections from user nodes
54    pub private_p2p_addr: LxSocketAddress,
55
56    // -- LSP -> User fees -- //
57    /// LSP's configured base fee for forwarding over LSP -> User channels.
58    ///
59    /// - For inbound payments, this fee is encoded in the invoice route hints
60    ///   (as part of the `RoutingFees` struct)
61    /// - Also used to estimate how much can be sent to another Lexe user.
62    pub lsp_usernode_base_fee_msat: u32,
63    /// LSP's configured prop fee for forwarding over LSP -> User channels.
64    ///
65    /// - For inbound payments, this fee is encoded in the invoice route hints
66    ///   (as part of the `RoutingFees` struct)
67    /// - Also used to estimate how much can be sent to another Lexe user.
68    pub lsp_usernode_prop_fee_ppm: u32,
69
70    // -- LSP -> External fees -- //
71    /// LSP's configured prop fee for forwarding over LSP -> External channels.
72    pub lsp_external_prop_fee_ppm: u32,
73    /// LSP's configured base fee for forwarding over LSP -> External channels.
74    pub lsp_external_base_fee_msat: u32,
75
76    // -- RouteHintHop fields -- //
77    pub cltv_expiry_delta: u16,
78    pub htlc_minimum_msat: u64,
79    pub htlc_maximum_msat: u64,
80}
81
82/// Information about Lexe's LSP's fees.
83// TODO(max): It would be nice if these were included in `LspInfo` as
84// `LspInfo::lsp_fees` with `#[serde(flatten)]` for forward compatibility,
85// but this struct uses newtypes, so we'd have to write some custom serde
86// attributes to (de)serialize as msat / ppm to make that work.
87#[derive(Copy, Clone, Debug, Eq, PartialEq)]
88pub struct LspFees {
89    /// The Lsp -> User base fee as an [`Amount`].
90    pub lsp_usernode_base_fee: Amount,
91    /// The Lsp -> User prop fee as a [`Decimal`], i.e. ppm / 1_000_000.
92    pub lsp_usernode_prop_fee: Decimal,
93    /// The Lsp -> External base fee as an [`Amount`].
94    pub lsp_external_base_fee: Amount,
95    /// The Lsp -> External prop fee as a [`Decimal`], i.e. ppm / 1_000_000.
96    pub lsp_external_prop_fee: Decimal,
97}
98
99/// Configuration info relating to Google OAuth2. When combined with an auth
100/// `code`, can be used to obtain a GDrive access token and refresh token.
101#[cfg_attr(test, derive(Arbitrary))]
102#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
103pub struct OAuthConfig {
104    #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
105    pub client_id: String,
106    #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
107    pub client_secret: String,
108    #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
109    pub redirect_uri: String,
110}
111
112// --- impl LspInfo --- //
113
114impl LspInfo {
115    /// Get the [`LspFees`] from this [`LspInfo`].
116    pub fn lsp_fees(&self) -> LspFees {
117        let lsp_usernode_base_fee =
118            Amount::from_msat(u64::from(self.lsp_usernode_base_fee_msat));
119        let lsp_usernode_prop_fee =
120            Decimal::from(self.lsp_usernode_prop_fee_ppm)
121                .checked_div(dec!(1_000_000))
122                .expect("Can't overflow because divisor is > 1");
123
124        let lsp_external_base_fee =
125            Amount::from_msat(u64::from(self.lsp_external_base_fee_msat));
126        let lsp_external_prop_fee =
127            Decimal::from(self.lsp_external_prop_fee_ppm)
128                .checked_div(dec!(1_000_000))
129                .expect("Can't overflow because divisor is > 1");
130
131        LspFees {
132            lsp_usernode_base_fee,
133            lsp_usernode_prop_fee,
134            lsp_external_base_fee,
135            lsp_external_prop_fee,
136        }
137    }
138
139    /// Returns a dummy [`LspInfo`] which can be used in tests.
140    #[cfg(any(test, feature = "test-utils"))]
141    pub fn dummy() -> Self {
142        use std::net::Ipv6Addr;
143
144        use lexe_common::root_seed::RootSeed;
145        use lexe_crypto::rng::FastRng;
146
147        let mut rng = FastRng::from_u64(20230216);
148        let node_pk = RootSeed::from_rng(&mut rng).derive_node_pk();
149        let addr = LxSocketAddress::TcpIpv6 {
150            ip: Ipv6Addr::LOCALHOST,
151            port: 42069,
152        };
153
154        Self {
155            node_pk,
156            private_p2p_addr: addr,
157            lsp_usernode_base_fee_msat: 0,
158            lsp_usernode_prop_fee_ppm: 4250,
159            lsp_external_base_fee_msat: 0,
160            lsp_external_prop_fee_ppm: 750,
161            cltv_expiry_delta: 72,
162            htlc_minimum_msat: 1,
163            htlc_maximum_msat: u64::MAX,
164        }
165    }
166}
167
168impl FromStr for LspInfo {
169    type Err = anyhow::Error;
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        serde_json::from_str(s).context("Invalid JSON")
172    }
173}
174
175impl Display for LspInfo {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        let json_str = serde_json::to_string(&self)
178            .expect("Does not contain map with non-string keys");
179        write!(f, "{json_str}")
180    }
181}
182
183// --- impl OAuthConfig --- //
184
185impl fmt::Debug for OAuthConfig {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        let client_id = &self.client_id;
188        let redirect_uri = &self.redirect_uri;
189        write!(
190            f,
191            "OAuthConfig {{ \
192                client_id: {client_id}, \
193                redirect_uri: {redirect_uri}, \
194                .. \
195            }}"
196        )
197    }
198}
199
200impl FromStr for OAuthConfig {
201    type Err = anyhow::Error;
202    fn from_str(s: &str) -> Result<Self, Self::Err> {
203        serde_json::from_str(s).context("Invalid JSON")
204    }
205}
206
207impl Display for OAuthConfig {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        let json_str = serde_json::to_string(&self)
210            .expect("Does not contain map with non-string keys");
211        write!(f, "{json_str}")
212    }
213}
214
215#[cfg(test)]
216mod test {
217    use lexe_common::test_utils::roundtrip;
218
219    use super::*;
220
221    #[test]
222    fn lsp_info_roundtrip() {
223        roundtrip::json_value_roundtrip_proptest::<LspInfo>();
224        roundtrip::fromstr_display_roundtrip_proptest::<LspInfo>();
225    }
226
227    #[test]
228    fn oauth_config_roundtrip() {
229        roundtrip::json_value_roundtrip_proptest::<OAuthConfig>();
230        roundtrip::fromstr_display_roundtrip_proptest::<OAuthConfig>();
231    }
232}