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    ppm::Ppm,
10};
11#[cfg(test)]
12use proptest_derive::Arbitrary;
13use rust_decimal::Decimal;
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    //
69    // node-v0.9.7: Renamed to `lsp_usernode_prop_fee`
70    #[serde(
71        rename = "lsp_usernode_prop_fee_ppm",
72        alias = "lsp_usernode_prop_fee"
73    )]
74    pub lsp_usernode_prop_fee: Ppm,
75
76    // -- LSP -> External fees -- //
77    /// LSP's configured prop fee for forwarding over LSP -> External channels.
78    //
79    // node-v0.9.7: Renamed to `lsp_external_prop_fee`
80    #[serde(
81        rename = "lsp_external_prop_fee_ppm",
82        alias = "lsp_external_prop_fee"
83    )]
84    pub lsp_external_prop_fee: Ppm,
85    /// LSP's configured base fee for forwarding over LSP -> External channels.
86    pub lsp_external_base_fee_msat: u32,
87
88    // -- RouteHintHop fields -- //
89    pub cltv_expiry_delta: u16,
90    pub htlc_minimum_msat: u64,
91    pub htlc_maximum_msat: u64,
92}
93
94/// Information about Lexe's LSP's fees.
95// TODO(max): It would be nice if these were included in `LspInfo` as
96// `LspInfo::lsp_fees` with `#[serde(flatten)]` for forward compatibility,
97// but this struct uses newtypes, so we'd have to write some custom serde
98// attributes to (de)serialize as msat / ppm to make that work.
99#[derive(Copy, Clone, Debug, Eq, PartialEq)]
100pub struct LspFees {
101    /// The Lsp -> User base fee as an [`Amount`].
102    pub lsp_usernode_base_fee: Amount,
103    /// The Lsp -> User prop fee as a [`Decimal`], i.e. ppm / 1_000_000.
104    pub lsp_usernode_prop_fee: Decimal,
105    /// The Lsp -> External base fee as an [`Amount`].
106    pub lsp_external_base_fee: Amount,
107    /// The Lsp -> External prop fee as a [`Decimal`], i.e. ppm / 1_000_000.
108    pub lsp_external_prop_fee: Decimal,
109}
110
111/// Configuration info relating to Google OAuth2. When combined with an auth
112/// `code`, can be used to obtain a GDrive access token and refresh token.
113#[cfg_attr(test, derive(Arbitrary))]
114#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
115pub struct OAuthConfig {
116    #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
117    pub client_id: String,
118    #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
119    pub client_secret: String,
120    #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
121    pub redirect_uri: String,
122}
123
124// --- impl LspInfo --- //
125
126impl LspInfo {
127    /// Get the [`LspFees`] from this [`LspInfo`].
128    pub fn lsp_fees(&self) -> LspFees {
129        let lsp_usernode_base_fee =
130            Amount::from_msat(u64::from(self.lsp_usernode_base_fee_msat));
131        let lsp_usernode_prop_fee = self.lsp_usernode_prop_fee.to_decimal();
132
133        let lsp_external_base_fee =
134            Amount::from_msat(u64::from(self.lsp_external_base_fee_msat));
135        let lsp_external_prop_fee = self.lsp_external_prop_fee.to_decimal();
136
137        LspFees {
138            lsp_usernode_base_fee,
139            lsp_usernode_prop_fee,
140            lsp_external_base_fee,
141            lsp_external_prop_fee,
142        }
143    }
144
145    /// Returns a dummy [`LspInfo`] which can be used in tests.
146    #[cfg(any(test, feature = "test-utils"))]
147    pub fn dummy() -> Self {
148        use std::net::Ipv6Addr;
149
150        use lexe_common::{ppm, root_seed::RootSeed};
151        use lexe_crypto::rng::FastRng;
152
153        let mut rng = FastRng::from_u64(20230216);
154        let node_pk = RootSeed::from_rng(&mut rng).derive_node_pk();
155        let addr = LxSocketAddress::TcpIpv6 {
156            ip: Ipv6Addr::LOCALHOST,
157            port: 42069,
158        };
159
160        Self {
161            node_pk,
162            private_p2p_addr: addr,
163            lsp_usernode_base_fee_msat: 0,
164            lsp_usernode_prop_fee: ppm!(0.425%),
165            lsp_external_base_fee_msat: 0,
166            lsp_external_prop_fee: ppm!(0.075%),
167            cltv_expiry_delta: 72,
168            htlc_minimum_msat: 1,
169            htlc_maximum_msat: u64::MAX,
170        }
171    }
172}
173
174impl FromStr for LspInfo {
175    type Err = anyhow::Error;
176    fn from_str(s: &str) -> Result<Self, Self::Err> {
177        serde_json::from_str(s).context("Invalid JSON")
178    }
179}
180
181impl Display for LspInfo {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        let json_str = serde_json::to_string(&self)
184            .expect("Does not contain map with non-string keys");
185        write!(f, "{json_str}")
186    }
187}
188
189// --- impl OAuthConfig --- //
190
191impl fmt::Debug for OAuthConfig {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        let client_id = &self.client_id;
194        let redirect_uri = &self.redirect_uri;
195        write!(
196            f,
197            "OAuthConfig {{ \
198                client_id: {client_id}, \
199                redirect_uri: {redirect_uri}, \
200                .. \
201            }}"
202        )
203    }
204}
205
206impl FromStr for OAuthConfig {
207    type Err = anyhow::Error;
208    fn from_str(s: &str) -> Result<Self, Self::Err> {
209        serde_json::from_str(s).context("Invalid JSON")
210    }
211}
212
213impl Display for OAuthConfig {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        let json_str = serde_json::to_string(&self)
216            .expect("Does not contain map with non-string keys");
217        write!(f, "{json_str}")
218    }
219}
220
221#[cfg(test)]
222mod test {
223    use lexe_common::test_utils::roundtrip;
224
225    use super::*;
226
227    #[test]
228    fn lsp_info_roundtrip() {
229        roundtrip::json_value_roundtrip_proptest::<LspInfo>();
230        roundtrip::fromstr_display_roundtrip_proptest::<LspInfo>();
231    }
232
233    #[test]
234    fn oauth_config_roundtrip() {
235        roundtrip::json_value_roundtrip_proptest::<OAuthConfig>();
236        roundtrip::fromstr_display_roundtrip_proptest::<OAuthConfig>();
237    }
238}