use std::fmt;
use std::time::Duration;
use bitcoin::secp256k1::PublicKey;
use bitcoin::Network;
use lightning::ln::msgs::SocketAddress;
use lightning::routing::gossip::NodeAlias;
use lightning::routing::router::RouteParametersConfig;
use lightning::util::config::{
ChannelConfig as LdkChannelConfig, MaxDustHTLCExposure as LdkMaxDustHTLCExposure, UserConfig,
};
use crate::logger::LogLevel;
const DEFAULT_NETWORK: Network = Network::Bitcoin;
const DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS: u64 = 80;
const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30;
const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10;
const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3;
const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000;
pub const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Debug;
pub const DEFAULT_LOG_FILENAME: &'static str = "ldk_node.log";
pub const DEFAULT_STORAGE_DIR_PATH: &str = "/tmp/ldk_node";
pub(crate) const DEFAULT_ESPLORA_SERVER_URL: &str = "https://blockstream.info/api";
pub(crate) const DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS: u64 = 10;
pub(crate) const BDK_CLIENT_STOP_GAP: usize = 20;
pub(crate) const BDK_CLIENT_CONCURRENCY: usize = 4;
pub(crate) const LDK_PAYMENT_RETRY_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) const RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL: u32 = 6;
pub(crate) const PEER_RECONNECTION_INTERVAL: Duration = Duration::from_secs(60);
pub(crate) const RGS_SYNC_INTERVAL: Duration = Duration::from_secs(60 * 60);
pub(crate) const EXTERNAL_PATHFINDING_SCORES_SYNC_INTERVAL: Duration = Duration::from_secs(60 * 60);
pub(crate) const NODE_ANN_BCAST_INTERVAL: Duration = Duration::from_secs(60 * 60);
pub(crate) const WALLET_SYNC_INTERVAL_MINIMUM_SECS: u64 = 10;
pub(crate) const BDK_WALLET_SYNC_TIMEOUT_SECS: u64 = 20;
pub(crate) const LDK_WALLET_SYNC_TIMEOUT_SECS: u64 = 10;
pub(crate) const LDK_EVENT_HANDLER_SHUTDOWN_TIMEOUT_SECS: u64 = 30;
pub(crate) const BACKGROUND_TASK_SHUTDOWN_TIMEOUT_SECS: u64 = 5;
pub(crate) const FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS: u64 = 5;
pub(crate) const TX_BROADCAST_TIMEOUT_SECS: u64 = 5;
pub(crate) const RGS_SYNC_TIMEOUT_SECS: u64 = 5;
pub const WALLET_KEYS_SEED_LEN: usize = 64;
pub(crate) const EXTERNAL_PATHFINDING_SCORES_SYNC_TIMEOUT_SECS: u64 = 5;
#[derive(Debug, Clone)]
pub struct Config {
pub storage_dir_path: String,
pub network: Network,
pub listening_addresses: Option<Vec<SocketAddress>>,
pub announcement_addresses: Option<Vec<SocketAddress>>,
pub node_alias: Option<NodeAlias>,
pub trusted_peers_0conf: Vec<PublicKey>,
pub probing_liquidity_limit_multiplier: u64,
pub anchor_channels_config: Option<AnchorChannelsConfig>,
pub route_parameters: Option<RouteParametersConfig>,
}
impl Default for Config {
fn default() -> Self {
Self {
storage_dir_path: DEFAULT_STORAGE_DIR_PATH.to_string(),
network: DEFAULT_NETWORK,
listening_addresses: None,
announcement_addresses: None,
trusted_peers_0conf: Vec::new(),
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
anchor_channels_config: Some(AnchorChannelsConfig::default()),
route_parameters: None,
node_alias: None,
}
}
}
#[derive(Debug, Clone)]
pub struct AnchorChannelsConfig {
pub trusted_peers_no_reserve: Vec<PublicKey>,
pub per_channel_reserve_sats: u64,
}
impl Default for AnchorChannelsConfig {
fn default() -> Self {
Self {
trusted_peers_no_reserve: Vec::new(),
per_channel_reserve_sats: DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS,
}
}
}
pub fn default_config() -> Config {
Config::default()
}
#[derive(Debug, PartialEq)]
pub(crate) enum AnnounceError {
MissingNodeAlias,
MissingListeningAddresses,
MissingAliasAndAddresses,
}
impl fmt::Display for AnnounceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AnnounceError::MissingNodeAlias => write!(f, "Node alias is not configured"),
AnnounceError::MissingListeningAddresses => {
write!(f, "Listening addresses are not configured")
},
AnnounceError::MissingAliasAndAddresses => {
write!(f, "Node alias and listening addresses are not configured")
},
}
}
}
pub(crate) fn may_announce_channel(config: &Config) -> Result<(), AnnounceError> {
let has_listening_addresses =
config.listening_addresses.as_ref().map_or(false, |addrs| !addrs.is_empty());
match (config.node_alias.is_some(), has_listening_addresses) {
(true, true) => Ok(()),
(true, false) => Err(AnnounceError::MissingListeningAddresses),
(false, true) => Err(AnnounceError::MissingNodeAlias),
(false, false) => Err(AnnounceError::MissingAliasAndAddresses),
}
}
pub(crate) fn default_user_config(config: &Config) -> UserConfig {
let mut user_config = UserConfig::default();
user_config.channel_handshake_limits.force_announced_channel_preference = false;
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;
if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
user_config.channel_handshake_config.announce_for_forwarding = false;
user_config.channel_handshake_limits.force_announced_channel_preference = true;
}
user_config
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct BackgroundSyncConfig {
pub onchain_wallet_sync_interval_secs: u64,
pub lightning_wallet_sync_interval_secs: u64,
pub fee_rate_cache_update_interval_secs: u64,
}
impl Default for BackgroundSyncConfig {
fn default() -> Self {
Self {
onchain_wallet_sync_interval_secs: DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS,
lightning_wallet_sync_interval_secs: DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS,
fee_rate_cache_update_interval_secs: DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct EsploraSyncConfig {
pub background_sync_config: Option<BackgroundSyncConfig>,
}
impl Default for EsploraSyncConfig {
fn default() -> Self {
Self { background_sync_config: Some(BackgroundSyncConfig::default()) }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ElectrumSyncConfig {
pub background_sync_config: Option<BackgroundSyncConfig>,
}
impl Default for ElectrumSyncConfig {
fn default() -> Self {
Self { background_sync_config: Some(BackgroundSyncConfig::default()) }
}
}
#[derive(Debug, Clone)]
pub struct BitcoindRestClientConfig {
pub rest_host: String,
pub rest_port: u16,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ChannelConfig {
pub forwarding_fee_proportional_millionths: u32,
pub forwarding_fee_base_msat: u32,
pub cltv_expiry_delta: u16,
pub max_dust_htlc_exposure: MaxDustHTLCExposure,
pub force_close_avoidance_max_fee_satoshis: u64,
pub accept_underpaying_htlcs: bool,
}
impl From<LdkChannelConfig> for ChannelConfig {
fn from(value: LdkChannelConfig) -> Self {
Self {
forwarding_fee_proportional_millionths: value.forwarding_fee_proportional_millionths,
forwarding_fee_base_msat: value.forwarding_fee_base_msat,
cltv_expiry_delta: value.cltv_expiry_delta,
max_dust_htlc_exposure: value.max_dust_htlc_exposure.into(),
force_close_avoidance_max_fee_satoshis: value.force_close_avoidance_max_fee_satoshis,
accept_underpaying_htlcs: value.accept_underpaying_htlcs,
}
}
}
impl From<ChannelConfig> for LdkChannelConfig {
fn from(value: ChannelConfig) -> Self {
Self {
forwarding_fee_proportional_millionths: value.forwarding_fee_proportional_millionths,
forwarding_fee_base_msat: value.forwarding_fee_base_msat,
cltv_expiry_delta: value.cltv_expiry_delta,
max_dust_htlc_exposure: value.max_dust_htlc_exposure.into(),
force_close_avoidance_max_fee_satoshis: value.force_close_avoidance_max_fee_satoshis,
accept_underpaying_htlcs: value.accept_underpaying_htlcs,
}
}
}
impl Default for ChannelConfig {
fn default() -> Self {
LdkChannelConfig::default().into()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MaxDustHTLCExposure {
FixedLimit {
limit_msat: u64,
},
FeeRateMultiplier {
multiplier: u64,
},
}
impl From<LdkMaxDustHTLCExposure> for MaxDustHTLCExposure {
fn from(value: LdkMaxDustHTLCExposure) -> Self {
match value {
LdkMaxDustHTLCExposure::FixedLimitMsat(limit_msat) => Self::FixedLimit { limit_msat },
LdkMaxDustHTLCExposure::FeeRateMultiplier(multiplier) => {
Self::FeeRateMultiplier { multiplier }
},
}
}
}
impl From<MaxDustHTLCExposure> for LdkMaxDustHTLCExposure {
fn from(value: MaxDustHTLCExposure) -> Self {
match value {
MaxDustHTLCExposure::FixedLimit { limit_msat } => Self::FixedLimitMsat(limit_msat),
MaxDustHTLCExposure::FeeRateMultiplier { multiplier } => {
Self::FeeRateMultiplier(multiplier)
},
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum AsyncPaymentsRole {
Client,
Server,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::{may_announce_channel, AnnounceError, Config, NodeAlias, SocketAddress};
#[test]
fn node_announce_channel() {
let mut node_config = Config::default();
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingAliasAndAddresses)
);
let alias_frm_str = |alias: &str| {
let mut bytes = [0u8; 32];
bytes[..alias.as_bytes().len()].copy_from_slice(alias.as_bytes());
NodeAlias(bytes)
};
node_config.node_alias = Some(alias_frm_str("LDK_Node"));
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingListeningAddresses)
);
let announcement_address = SocketAddress::from_str("123.45.67.89:9735")
.expect("Socket address conversion failed.");
node_config.announcement_addresses = Some(vec![announcement_address]);
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingListeningAddresses)
);
node_config.listening_addresses = Some(vec![]);
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingListeningAddresses)
);
let socket_address =
SocketAddress::from_str("localhost:8000").expect("Socket address conversion failed.");
if let Some(ref mut addresses) = node_config.listening_addresses {
addresses.push(socket_address);
}
assert!(may_announce_channel(&node_config).is_ok());
}
}