use std::{fmt, fmt::Display, path::Path, str::FromStr};
use anyhow::Context;
#[cfg(test)]
use lexe_common::test_utils::arbitrary;
use lexe_common::{
api::user::NodePk,
dec,
ln::{addr::LxSocketAddress, amount::Amount},
};
#[cfg(test)]
use proptest_derive::Arbitrary;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
pub mod node;
pub trait EnclaveArgs: Serialize + DeserializeOwned {
const NAME: &str;
fn to_command(&self, bin_path: &Path) -> std::process::Command {
let mut command = std::process::Command::new(bin_path);
self.append_args(&mut command);
command
}
fn append_args(&self, cmd: &mut std::process::Command) {
cmd.arg(Self::NAME).arg(self.to_json_string());
}
fn from_json_str(json_str: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json_str)
}
fn to_json_string(&self) -> String {
serde_json::to_string(self).expect("JSON serialization failed")
}
}
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LspInfo {
pub node_pk: NodePk,
pub private_p2p_addr: LxSocketAddress,
pub lsp_usernode_base_fee_msat: u32,
pub lsp_usernode_prop_fee_ppm: u32,
pub lsp_external_prop_fee_ppm: u32,
pub lsp_external_base_fee_msat: u32,
pub cltv_expiry_delta: u16,
pub htlc_minimum_msat: u64,
pub htlc_maximum_msat: u64,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct LspFees {
pub lsp_usernode_base_fee: Amount,
pub lsp_usernode_prop_fee: Decimal,
pub lsp_external_base_fee: Amount,
pub lsp_external_prop_fee: Decimal,
}
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct OAuthConfig {
#[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
pub client_id: String,
#[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
pub client_secret: String,
#[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
pub redirect_uri: String,
}
impl LspInfo {
pub fn lsp_fees(&self) -> LspFees {
let lsp_usernode_base_fee =
Amount::from_msat(u64::from(self.lsp_usernode_base_fee_msat));
let lsp_usernode_prop_fee =
Decimal::from(self.lsp_usernode_prop_fee_ppm)
.checked_div(dec!(1_000_000))
.expect("Can't overflow because divisor is > 1");
let lsp_external_base_fee =
Amount::from_msat(u64::from(self.lsp_external_base_fee_msat));
let lsp_external_prop_fee =
Decimal::from(self.lsp_external_prop_fee_ppm)
.checked_div(dec!(1_000_000))
.expect("Can't overflow because divisor is > 1");
LspFees {
lsp_usernode_base_fee,
lsp_usernode_prop_fee,
lsp_external_base_fee,
lsp_external_prop_fee,
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn dummy() -> Self {
use std::net::Ipv6Addr;
use lexe_common::root_seed::RootSeed;
use lexe_crypto::rng::FastRng;
let mut rng = FastRng::from_u64(20230216);
let node_pk = RootSeed::from_rng(&mut rng).derive_node_pk();
let addr = LxSocketAddress::TcpIpv6 {
ip: Ipv6Addr::LOCALHOST,
port: 42069,
};
Self {
node_pk,
private_p2p_addr: addr,
lsp_usernode_base_fee_msat: 0,
lsp_usernode_prop_fee_ppm: 4250,
lsp_external_base_fee_msat: 0,
lsp_external_prop_fee_ppm: 750,
cltv_expiry_delta: 72,
htlc_minimum_msat: 1,
htlc_maximum_msat: u64::MAX,
}
}
}
impl FromStr for LspInfo {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).context("Invalid JSON")
}
}
impl Display for LspInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json_str = serde_json::to_string(&self)
.expect("Does not contain map with non-string keys");
write!(f, "{json_str}")
}
}
impl fmt::Debug for OAuthConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let client_id = &self.client_id;
let redirect_uri = &self.redirect_uri;
write!(
f,
"OAuthConfig {{ \
client_id: {client_id}, \
redirect_uri: {redirect_uri}, \
.. \
}}"
)
}
}
impl FromStr for OAuthConfig {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).context("Invalid JSON")
}
}
impl Display for OAuthConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json_str = serde_json::to_string(&self)
.expect("Does not contain map with non-string keys");
write!(f, "{json_str}")
}
}
#[cfg(test)]
mod test {
use lexe_common::test_utils::roundtrip;
use super::*;
#[test]
fn lsp_info_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<LspInfo>();
roundtrip::fromstr_display_roundtrip_proptest::<LspInfo>();
}
#[test]
fn oauth_config_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<OAuthConfig>();
roundtrip::fromstr_display_roundtrip_proptest::<OAuthConfig>();
}
}