Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set node alias #330

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A ready-to-go Lightning node library built using [LDK][ldk] and [BDK][bdk].
LDK Node is a self-custodial Lightning node in library form. Its central goal is to provide a small, simple, and straightforward interface that enables users to easily set up and run a Lightning node with an integrated on-chain wallet. While minimalism is at its core, LDK Node aims to be sufficiently modular and configurable to be useful for a variety of use cases.

## Getting Started
The primary abstraction of the library is the [`Node`][api_docs_node], which can be retrieved by setting up and configuring a [`Builder`][api_docs_builder] to your liking and calling one of the `build` methods. `Node` can then be controlled via commands such as `start`, `stop`, `connect_open_channel`, `send`, etc.
The primary abstraction of the library is the [`Node`][api_docs_node], which can be retrieved by setting up and configuring a [`Builder`][api_docs_builder] to your liking and calling one of the `build` methods. `Node` can then be controlled via commands such as `start`, `stop`, `open_channel`, `open_announced_channel`, `send`, etc.

```rust
use ldk_node::Builder;
Expand All @@ -37,7 +37,7 @@ fn main() {

let node_id = PublicKey::from_str("NODE_ID").unwrap();
let node_addr = SocketAddress::from_str("IP_ADDR:PORT").unwrap();
node.connect_open_channel(node_id, node_addr, 10000, None, None, false).unwrap();
node.open_channel(node_id, node_addr, 10000, None, None).unwrap();

let event = node.wait_next_event();
println!("EVENT: {:?}", event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class LibraryTest {
assertEquals(100000uL, totalBalance1)
assertEquals(100000uL, totalBalance2)

node1.connectOpenChannel(nodeId2, listenAddress2, 50000u, null, null, true)
node1.openChannel(nodeId2, listenAddress2, 50000u, null, null)

val channelPendingEvent1 = node1.waitNextEvent()
println("Got event: $channelPendingEvent1")
Expand Down
14 changes: 13 additions & 1 deletion bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dictionary Config {
string? log_dir_path;
Network network;
sequence<SocketAddress>? listening_addresses;
NodeAlias? node_alias;
u64 onchain_wallet_sync_interval_secs;
u64 wallet_sync_interval_secs;
u64 fee_rate_cache_update_interval_secs;
Expand Down Expand Up @@ -40,6 +41,8 @@ interface Builder {
[Throws=BuildError]
void set_listening_addresses(sequence<SocketAddress> listening_addresses);
[Throws=BuildError]
void set_node_alias(string node_alias);
[Throws=BuildError]
Node build();
[Throws=BuildError]
Node build_with_fs_store();
Expand All @@ -59,6 +62,7 @@ interface Node {
void event_handled();
PublicKey node_id();
sequence<SocketAddress>? listening_addresses();
NodeAlias? node_alias();
Bolt11Payment bolt11_payment();
Bolt12Payment bolt12_payment();
SpontaneousPayment spontaneous_payment();
Expand All @@ -69,7 +73,9 @@ interface Node {
[Throws=NodeError]
void disconnect(PublicKey node_id);
[Throws=NodeError]
UserChannelId connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel);
UserChannelId open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
Expand Down Expand Up @@ -202,6 +208,7 @@ enum NodeError {
"InvalidNetwork",
"InvalidUri",
"InvalidQuantity",
"InvalidNodeAlias",
"DuplicatePayment",
"UnsupportedCurrency",
"InsufficientFunds",
Expand Down Expand Up @@ -232,12 +239,14 @@ enum BuildError {
"InvalidSystemTime",
"InvalidChannelMonitor",
"InvalidListeningAddresses",
"InvalidNodeAlias",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"KVStoreSetupFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
"InvalidNodeAlias"
};

[Enum]
Expand Down Expand Up @@ -529,3 +538,6 @@ typedef string Mnemonic;

[Custom]
typedef string UntrustedString;

[Custom]
typedef string NodeAlias;
2 changes: 1 addition & 1 deletion bindings/python/src/ldk_node/test_ldk_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def test_channel_full_cycle(self):
print("TOTAL 2:", total_balance_2)
self.assertEqual(total_balance_2, 100000)

node_1.connect_open_channel(node_id_2, listening_addresses_2[0], 50000, None, None, True)
node_1.open_channel(node_id_2, listening_addresses_2[0], 50000, None, None)

channel_pending_event_1 = node_1.wait_next_event()
assert isinstance(channel_pending_event_1, Event.CHANNEL_PENDING)
Expand Down
76 changes: 75 additions & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use lightning::chain::{chainmonitor, BestBlock, Watch};
use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs};
use lightning::ln::msgs::{RoutingMessageHandler, SocketAddress};
use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler};
use lightning::routing::gossip::NodeAlias;
use lightning::routing::router::DefaultRouter;
use lightning::routing::scoring::{
ProbabilisticScorer, ProbabilisticScoringDecayParameters, ProbabilisticScoringFeeParameters,
Expand Down Expand Up @@ -109,7 +110,7 @@ impl Default for LiquiditySourceConfig {
/// An error encountered during building a [`Node`].
///
/// [`Node`]: crate::Node
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum BuildError {
/// The given seed bytes are invalid, e.g., have invalid length.
InvalidSeedBytes,
Expand All @@ -121,6 +122,8 @@ pub enum BuildError {
InvalidChannelMonitor,
/// The given listening addresses are invalid, e.g. too many were passed.
InvalidListeningAddresses,
/// The provided alias is invalid.
InvalidNodeAlias,
/// We failed to read data from the [`KVStore`].
///
/// [`KVStore`]: lightning::util::persist::KVStore
Expand Down Expand Up @@ -159,6 +162,7 @@ impl fmt::Display for BuildError {
Self::KVStoreSetupFailed => write!(f, "Failed to setup KVStore."),
Self::WalletSetupFailed => write!(f, "Failed to setup onchain wallet."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
Self::InvalidNodeAlias => write!(f, "Given node alias is invalid."),
}
}
}
Expand Down Expand Up @@ -303,6 +307,16 @@ impl NodeBuilder {
Ok(self)
}

/// Sets the alias the [`Node`] will use in its announcement.
///
/// The provided alias must be a valid UTF-8 string.
pub fn set_node_alias(&mut self, node_alias: String) -> Result<&mut Self, BuildError> {
let node_alias = sanitize_alias(&node_alias)?;

self.config.node_alias = Some(node_alias);
Ok(self)
}

/// Sets the level at which [`Node`] will log messages.
pub fn set_log_level(&mut self, level: LogLevel) -> &mut Self {
self.config.log_level = level;
Expand Down Expand Up @@ -501,6 +515,11 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_listening_addresses(listening_addresses).map(|_| ())
}

/// Sets the node alias.
pub fn set_node_alias(&self, node_alias: String) -> Result<(), BuildError> {
self.inner.write().unwrap().set_node_alias(node_alias).map(|_| ())
}

/// Sets the level at which [`Node`] will log messages.
pub fn set_log_level(&self, level: LogLevel) {
self.inner.write().unwrap().set_log_level(level);
Expand Down Expand Up @@ -1050,3 +1069,58 @@ fn seed_bytes_from_config(
},
}
}

/// Sanitize the user-provided node alias to ensure that it is a valid protocol-specified UTF-8 string.
pub(crate) fn sanitize_alias(alias_str: &str) -> Result<NodeAlias, BuildError> {
let alias = alias_str.trim();

// Alias must be 32-bytes long or less.
if alias.as_bytes().len() > 32 {
return Err(BuildError::InvalidNodeAlias);
}

let mut bytes = [0u8; 32];
bytes[..alias.as_bytes().len()].copy_from_slice(alias.as_bytes());
Ok(NodeAlias(bytes))
}

#[cfg(test)]
mod tests {
use super::{sanitize_alias, BuildError, NodeAlias};

#[test]
fn sanitize_empty_node_alias() {
// Empty node alias
let alias = "";
let mut buf = [0u8; 32];
buf[..alias.as_bytes().len()].copy_from_slice(alias.as_bytes());

let expected_node_alias = NodeAlias([0; 32]);
let node_alias = sanitize_alias(alias).unwrap();
assert_eq!(node_alias, expected_node_alias);
}

#[test]
fn sanitize_alias_with_sandwiched_null() {
// Alias with emojis
let alias = "I\u{1F496}LDK-Node!";
let mut buf = [0u8; 32];
buf[..alias.as_bytes().len()].copy_from_slice(alias.as_bytes());
let expected_alias = NodeAlias(buf);

let user_provided_alias = "I\u{1F496}LDK-Node!\0\u{26A1}";
let node_alias = sanitize_alias(user_provided_alias).unwrap();

let node_alias_display = format!("{}", node_alias);

assert_eq!(alias, &node_alias_display);
assert_ne!(expected_alias, node_alias);
}

#[test]
fn sanitize_alias_gt_32_bytes() {
let alias = "This is a string longer than thirty-two bytes!"; // 46 bytes
let node = sanitize_alias(alias);
assert_eq!(node.err().unwrap(), BuildError::InvalidNodeAlias);
}
}
124 changes: 121 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use crate::payment::SendingParameters;

use lightning::ln::msgs::SocketAddress;
use lightning::routing::gossip::NodeAlias;
use lightning::util::config::UserConfig;
use lightning::util::logger::Level as LogLevel;

Expand Down Expand Up @@ -86,6 +87,7 @@ pub(crate) const WALLET_KEYS_SEED_LEN: usize = 64;
/// | `log_dir_path` | None |
/// | `network` | Bitcoin |
/// | `listening_addresses` | None |
/// | `node_alias` | None |
/// | `default_cltv_expiry_delta` | 144 |
/// | `onchain_wallet_sync_interval_secs` | 80 |
/// | `wallet_sync_interval_secs` | 30 |
Expand All @@ -110,7 +112,15 @@ pub struct Config {
/// The used Bitcoin network.
pub network: Network,
/// The addresses on which the node will listen for incoming connections.
///
/// **Note**: Node announcements will only be broadcast if the `node_alias` and the
/// `listening_addresses` are set.
pub listening_addresses: Option<Vec<SocketAddress>>,
/// The node alias to be used in announcements.
///
/// **Note**: Node announcements will only be broadcast if the `node_alias` and the
/// `listening_addresses` are set.
pub node_alias: Option<NodeAlias>,
/// The time in-between background sync attempts of the onchain wallet, in seconds.
///
/// **Note:** A minimum of 10 seconds is always enforced.
Expand Down Expand Up @@ -180,6 +190,7 @@ impl Default for Config {
log_level: DEFAULT_LOG_LEVEL,
anchor_channels_config: Some(AnchorChannelsConfig::default()),
sending_parameters: None,
node_alias: None,
}
}
}
Expand Down Expand Up @@ -265,17 +276,124 @@ pub fn default_config() -> Config {
Config::default()
}

/// Specifies reasons why a channel cannot be announced.
#[derive(Debug, PartialEq)]
pub(crate) enum ChannelAnnouncementBlocker {
/// The node alias is not set.
MissingNodeAlias,
/// The listening addresses are not set.
MissingListeningAddresses,
// This listening addresses is set but the vector is empty.
EmptyListeningAddresses,
}

/// Enumeration defining the announcement status of a channel.
#[derive(Debug, PartialEq)]
pub(crate) enum ChannelAnnouncementStatus {
/// The channel is announceable.
Announceable,
/// The channel is not announceable.
Unannounceable(ChannelAnnouncementBlocker),
}

/// Checks if a node is can announce a channel based on the configured values of both the node's
/// alias and its listening addresses.
///
/// If either of them is unset, the node cannot announce the channel. This ability to announce/
/// unannounce a channel is codified with `ChannelAnnouncementStatus`
pub(crate) fn can_announce_channel(config: &Config) -> ChannelAnnouncementStatus {
if config.node_alias.is_none() {
return ChannelAnnouncementStatus::Unannounceable(
ChannelAnnouncementBlocker::MissingNodeAlias,
);
}

match &config.listening_addresses {
None => ChannelAnnouncementStatus::Unannounceable(
ChannelAnnouncementBlocker::MissingListeningAddresses,
),
Some(addresses) if addresses.is_empty() => ChannelAnnouncementStatus::Unannounceable(
ChannelAnnouncementBlocker::EmptyListeningAddresses,
),
Some(_) => ChannelAnnouncementStatus::Announceable,
}
}

pub(crate) fn default_user_config(config: &Config) -> UserConfig {
// Initialize the default config values.
//
// Note that methods such as Node::connect_open_channel might override some of the values set
// here, e.g. the ChannelHandshakeConfig, meaning these default values will mostly be relevant
// for inbound channels.
// Note that methods such as Node::open_channel and Node::open_announced_channel might override
// some of the values set here, e.g. the ChannelHandshakeConfig, meaning these default values
// will mostly be relevant for inbound channels.
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();

match can_announce_channel(config) {
ChannelAnnouncementStatus::Announceable => (),
ChannelAnnouncementStatus::Unannounceable(_) => {
user_config.accept_forwards_to_priv_channels = false;
user_config.channel_handshake_config.announced_channel = false;
user_config.channel_handshake_limits.force_announced_channel_preference = true;
},
}

user_config
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use crate::config::ChannelAnnouncementStatus;

use super::can_announce_channel;
use super::Config;
use super::NodeAlias;
use super::SocketAddress;

#[test]
fn node_can_announce_channel() {
// Default configuration with node alias and listening addresses unset
let mut node_config = Config::default();
assert_eq!(
can_announce_channel(&node_config),
ChannelAnnouncementStatus::Unannounceable(
crate::config::ChannelAnnouncementBlocker::MissingNodeAlias
)
);

// Set node alias with listening addresses unset
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!(
can_announce_channel(&node_config),
ChannelAnnouncementStatus::Unannounceable(
crate::config::ChannelAnnouncementBlocker::MissingListeningAddresses
)
);

// Set node alias with an empty list of listening addresses
node_config.listening_addresses = Some(vec![]);
assert_eq!(
can_announce_channel(&node_config),
ChannelAnnouncementStatus::Unannounceable(
crate::config::ChannelAnnouncementBlocker::EmptyListeningAddresses
)
);

// Set node alias with a non-empty list of listening addresses
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_eq!(can_announce_channel(&node_config), ChannelAnnouncementStatus::Announceable);
}
}
Loading
Loading