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 12 commits into
base: main
Choose a base branch
from
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
14 changes: 13 additions & 1 deletion bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dictionary Config {
LogLevel log_level;
AnchorChannelsConfig? anchor_channels_config;
SendingParameters? sending_parameters;
NodeAlias? node_alias;
};

dictionary AnchorChannelsConfig {
Expand All @@ -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 Down Expand Up @@ -69,7 +72,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,11 +207,13 @@ enum NodeError {
"InvalidNetwork",
"InvalidUri",
"InvalidQuantity",
"InvalidNodeAlias",
"DuplicatePayment",
"UnsupportedCurrency",
"InsufficientFunds",
"LiquiditySourceUnavailable",
"LiquidityFeeTooHigh",
"OpenAnnouncedChannelFailed"
};

dictionary NodeStatus {
Expand All @@ -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;
77 changes: 76 additions & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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 @@ -102,7 +103,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 @@ -114,6 +115,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 @@ -152,6 +155,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 @@ -302,6 +306,15 @@ impl NodeBuilder {
self
}

/// Sets the alias the [`Node`] will use in its announcement. The provided
enigbe marked this conversation as resolved.
Show resolved Hide resolved
/// 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)
}

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Result<Node, BuildError> {
Expand Down Expand Up @@ -499,6 +512,11 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_log_level(level);
}

/// 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(|_| ())
}

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Result<Arc<Node>, BuildError> {
Expand Down Expand Up @@ -1043,3 +1061,60 @@ fn seed_bytes_from_config(
},
}
}

/// Sanitize the user-provided node alias to ensure that it is a valid protocol-specified UTF-8 string.
pub fn sanitize_alias(alias_str: &str) -> Result<NodeAlias, BuildError> {
enigbe marked this conversation as resolved.
Show resolved Hide resolved
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 lightning::routing::gossip::NodeAlias;

use crate::{builder::sanitize_alias, BuildError};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Given these tests are in a sub-module of builder, I think you don't need to import these types at all here?


#[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);
}
}
66 changes: 66 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,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 @@ -103,6 +104,9 @@ 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please tick node_alias and listening_addresses here and below, as they refer to field names.

/// listening_addresses are set.
pub listening_addresses: Option<Vec<SocketAddress>>,
/// The time in-between background sync attempts of the onchain wallet, in seconds.
///
Expand Down Expand Up @@ -156,6 +160,11 @@ pub struct Config {
/// **Note:** If unset, default parameters will be used, and you will be able to override the
/// parameters on a per-payment basis in the corresponding method calls.
pub sending_parameters: Option<SendingParameters>,
/// 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>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this up, just after listening_addresses? Also, could you add it (and the used default value) to the table in the docs above?

}

impl Default for Config {
Expand All @@ -173,6 +182,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 @@ -258,6 +268,17 @@ pub fn default_config() -> Config {
Config::default()
}

/// 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.
pub fn can_announce_channel(config: &Config) -> bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to expose this for now, let's drop pub or, if needed, make it pub(crate).

let are_addresses_set =
config.listening_addresses.clone().map_or(false, |addr_vec| !addr_vec.is_empty());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't clone here, you should be able to use as_ref().

let is_alias_set = config.node_alias.is_some();

is_alias_set && are_addresses_set
}

pub(crate) fn default_user_config(config: &Config) -> UserConfig {
// Initialize the default config values.
//
Expand All @@ -270,5 +291,50 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();

if !can_announce_channel(config) {
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 lightning::{ln::msgs::SocketAddress, routing::gossip::NodeAlias};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Let's make these individual use lines.


use crate::config::can_announce_channel;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: no need to import types from the super-module.


use super::Config;

#[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), false);

// 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), false);

// Set node alias with an empty list of listening addresses
node_config.listening_addresses = Some(vec![]);
assert_eq!(can_announce_channel(&node_config), false);

// 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), true);
}
}
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ pub enum Error {
InvalidUri,
/// The given quantity is invalid.
InvalidQuantity,
/// The given node alias is invalid.
InvalidNodeAlias,
/// A payment with the given hash has already been initiated.
DuplicatePayment,
/// The provided offer was denonminated in an unsupported currency.
Expand All @@ -101,6 +103,10 @@ pub enum Error {
LiquiditySourceUnavailable,
/// The given operation failed due to the LSP's required opening fee being too high.
LiquidityFeeTooHigh,
/// Returned when trying to open an announced channel with a peer. This
/// error occurs when a [`crate::Node`'s] alias or listening addresses
/// are unconfigured.
OpenAnnouncedChannelFailed,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we introduce two separate error variants (one for the listening addresses case, one for unset node alias)? We could possibly also re-use the InvalidNodeAlias variant and possibly add an InvalidListeningAddress, or even reuse InvalidSocketAddress?

}

impl fmt::Display for Error {
Expand Down Expand Up @@ -156,6 +162,7 @@ impl fmt::Display for Error {
Self::InvalidNetwork => write!(f, "The given network is invalid."),
Self::InvalidUri => write!(f, "The given URI is invalid."),
Self::InvalidQuantity => write!(f, "The given quantity is invalid."),
Self::InvalidNodeAlias => write!(f, "The given node alias is invalid."),
Self::DuplicatePayment => {
write!(f, "A payment with the given hash has already been initiated.")
},
Expand All @@ -171,6 +178,7 @@ impl fmt::Display for Error {
Self::LiquidityFeeTooHigh => {
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
},
Self::OpenAnnouncedChannelFailed => write!(f, "Failed to open an announced channel."),
}
}
}
Expand Down
Loading
Loading