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

Support separate node announcement addresses #484

Merged
merged 3 commits into from
Mar 28, 2025
Merged
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
5 changes: 5 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dictionary Config {
string storage_dir_path;
Network network;
sequence<SocketAddress>? listening_addresses;
sequence<SocketAddress>? announcement_addresses;
NodeAlias? node_alias;
sequence<PublicKey> trusted_peers_0conf;
u64 probing_liquidity_limit_multiplier;
Expand Down Expand Up @@ -84,6 +85,8 @@ interface Builder {
[Throws=BuildError]
void set_listening_addresses(sequence<SocketAddress> listening_addresses);
[Throws=BuildError]
void set_announcement_addresses(sequence<SocketAddress> announcement_addresses);
[Throws=BuildError]
void set_node_alias(string node_alias);
[Throws=BuildError]
Node build();
Expand Down Expand Up @@ -112,6 +115,7 @@ interface Node {
void event_handled();
PublicKey node_id();
sequence<SocketAddress>? listening_addresses();
sequence<SocketAddress>? announcement_addresses();
NodeAlias? node_alias();
Bolt11Payment bolt11_payment();
Bolt12Payment bolt12_payment();
Expand Down Expand Up @@ -319,6 +323,7 @@ enum BuildError {
"InvalidSystemTime",
"InvalidChannelMonitor",
"InvalidListeningAddresses",
"InvalidAnnouncementAddresses",
"InvalidNodeAlias",
"ReadFailed",
"WriteFailed",
Expand Down
56 changes: 52 additions & 4 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

use crate::chain::{ChainSource, DEFAULT_ESPLORA_SERVER_URL};
use crate::config::{
default_user_config, Config, EsploraSyncConfig, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
WALLET_KEYS_SEED_LEN,
default_user_config, may_announce_channel, AnnounceError, Config, EsploraSyncConfig,
DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN,
};

use crate::connection::ConnectionManager;
Expand All @@ -32,8 +32,7 @@ use crate::types::{
};
use crate::wallet::persist::KVStoreWalletPersister;
use crate::wallet::Wallet;
use crate::Node;
use crate::{io, NodeMetrics};
use crate::{io, Node, NodeMetrics};

use lightning::chain::{chainmonitor, BestBlock, Watch};
use lightning::io::Cursor;
Expand Down Expand Up @@ -148,6 +147,8 @@ pub enum BuildError {
InvalidChannelMonitor,
/// The given listening addresses are invalid, e.g. too many were passed.
InvalidListeningAddresses,
/// The given announcement addresses are invalid, e.g. too many were passed.
InvalidAnnouncementAddresses,
/// The provided alias is invalid.
InvalidNodeAlias,
/// We failed to read data from the [`KVStore`].
Expand Down Expand Up @@ -184,6 +185,9 @@ impl fmt::Display for BuildError {
write!(f, "Failed to watch a deserialized ChannelMonitor")
},
Self::InvalidListeningAddresses => write!(f, "Given listening addresses are invalid."),
Self::InvalidAnnouncementAddresses => {
write!(f, "Given announcement addresses are invalid.")
},
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => write!(f, "Failed to write to store."),
Self::StoragePathAccessFailed => write!(f, "Failed to access the given storage path."),
Expand Down Expand Up @@ -413,6 +417,22 @@ impl NodeBuilder {
Ok(self)
}

/// Sets the IP address and TCP port which [`Node`] will announce to the gossip network that it accepts connections on.
Copy link
Contributor

Choose a reason for hiding this comment

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

should we add the 'if unset' note here as well?

///
/// **Note**: If unset, the [`listening_addresses`] will be used as the list of addresses to announce.
///
/// [`listening_addresses`]: Self::set_listening_addresses
pub fn set_announcement_addresses(
&mut self, announcement_addresses: Vec<SocketAddress>,
) -> Result<&mut Self, BuildError> {
if announcement_addresses.len() > 100 {
return Err(BuildError::InvalidAnnouncementAddresses);
}

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

/// Sets the node alias that will be used when broadcasting announcements to the gossip
/// network.
///
Expand Down Expand Up @@ -776,6 +796,17 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_listening_addresses(listening_addresses).map(|_| ())
}

/// Sets the IP address and TCP port which [`Node`] will announce to the gossip network that it accepts connections on.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please align the docs on the uniffi variant with the non-uniffi variant.

///
/// **Note**: If unset, the [`listening_addresses`] will be used as the list of addresses to announce.
///
/// [`listening_addresses`]: Self::set_listening_addresses
pub fn set_announcement_addresses(
&self, announcement_addresses: Vec<SocketAddress>,
) -> Result<(), BuildError> {
self.inner.write().unwrap().set_announcement_addresses(announcement_addresses).map(|_| ())
}

/// Sets the node alias that will be used when broadcasting announcements to the gossip
/// network.
///
Expand Down Expand Up @@ -880,6 +911,23 @@ fn build_with_store_internal(
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
logger: Arc<Logger>, kv_store: Arc<DynStore>,
) -> Result<Node, BuildError> {
if let Err(err) = may_announce_channel(&config) {
if config.announcement_addresses.is_some() {
log_error!(logger, "Announcement addresses were set but some required configuration options for node announcement are missing: {}", err);
let build_error = if matches!(err, AnnounceError::MissingNodeAlias) {
BuildError::InvalidNodeAlias
} else {
BuildError::InvalidListeningAddresses
};
return Err(build_error);
}

if config.node_alias.is_some() {
log_error!(logger, "Node alias was set but some required configuration options for node announcement are missing: {}", err);
return Err(BuildError::InvalidListeningAddresses);
}
}

// Initialize the status fields.
let is_listening = Arc::new(AtomicBool::new(false));
let node_metrics = match read_node_metrics(Arc::clone(&kv_store), Arc::clone(&logger)) {
Expand Down
71 changes: 63 additions & 8 deletions src/config.rs
Copy link
Collaborator

@tnull tnull Mar 17, 2025

Choose a reason for hiding this comment

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

You'll also need to account for the new field in may_announce_channel (and corresponding tests) in this file. We use this method to check whether we allow to open or accept announced channels.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I looked at this but I believe the may_announce_channel is still correct as-is. announcement_addresses doesn't change the behavior of when or if a node can announce channels. That is still purely determined by listening_addresses and alias. The announcement_addresses just allows an override for what addresses are actually announced.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, you are right! In fact, could you extend the node_announce_channel test case to assert that may_announce_channel is still false if listening_addresses is None and announcement_addresses is Some?

May also be good to add a check for this and log+abort early in Builder::build_with_store_internal, so that the user can't ever startup with misconfigured Node where announcement_addresses is set but listening_addresses aren't.

Copy link
Contributor Author

@aylagreystone aylagreystone Mar 18, 2025

Choose a reason for hiding this comment

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

Both of those suggestions sound good. For the misconfigured node do we want a new error variant or should I re-use the existing BuildError::InvalidAnnouncementAddresses? I think it's probably fine as long as there's a log_error with explanation alongside it.

Also, I think the check should not only be against unset listening_addresses but actually be if config.announcement_addrresses.is_some() && !may_announce_channel(&config).

Basically if you set announcement addresses but the node will not announce then it's misconfigured.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Both of those suggestions sound good. For the misconfigured node do we want a new error variant or should I re-use the existing BuildError::InvalidAnnouncementAddresses? I think it's probably fine as long as there's a log_error with explanation alongside it.

Yeah, though InvalidListeningAddresses might make more sense, no?

Basically if you set announcement addresses but the node will not announce then it's misconfigured.

Yeah, that makes sense. FWIW, could do the same for NodeAlias, as you'd also only expect it to be set if you want to announce your node.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well if the check is if config.announcement_addrresses.is_some() && !may_announce_channel(&config) then we don't know if it's missing listening_addresses or alias and so maybe it makes more sense for the error to be BuildError::InvalidAnnouncementAddresses` in the sense they are invalid because you didn't set the other values? Or we could add a new error specifically for some announcement field set (announcement_addresses or alias) when the node is not going to announce.

Will add the same check for NodeAlias.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Well, maybe we should switch may_announce_channel to return a Result<(), AnnounceError>? Then, we know at the callsites why it can't announce and can take action (mostly log the right thing before returning the corresponding BuildError) based on the error type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gave this a shot in the latest commit. The error message handling feels a bit verbose now but I tried to dry it up best I could. Check it out and let me know what you think.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use lightning::util::config::UserConfig;
use bitcoin::secp256k1::PublicKey;
use bitcoin::Network;

use std::fmt;
use std::time::Duration;

// Config defaults
Expand Down Expand Up @@ -117,6 +118,12 @@ pub struct Config {
/// **Note**: We will only allow opening and accepting public channels if the `node_alias` and the
/// `listening_addresses` are set.
pub listening_addresses: Option<Vec<SocketAddress>>,
/// The addresses which the node will announce to the gossip network that it accepts connections on.
///
/// **Note**: If unset, the [`listening_addresses`] will be used as the list of addresses to announce.
///
/// [`listening_addresses`]: Config::listening_addresses
pub announcement_addresses: Option<Vec<SocketAddress>>,
/// The node alias that will be used when broadcasting announcements to the gossip network.
///
/// The provided alias must be a valid UTF-8 string and no longer than 32 bytes in total.
Expand Down Expand Up @@ -168,6 +175,7 @@ impl Default for Config {
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()),
Expand Down Expand Up @@ -256,9 +264,37 @@ pub fn default_config() -> Config {
Config::default()
}

pub(crate) fn may_announce_channel(config: &Config) -> bool {
config.node_alias.is_some()
&& config.listening_addresses.as_ref().map_or(false, |addrs| !addrs.is_empty())
#[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 {
Expand All @@ -273,7 +309,7 @@ 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 !may_announce_channel(config) {
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;
Expand Down Expand Up @@ -461,6 +497,7 @@ mod tests {
use std::str::FromStr;

use super::may_announce_channel;
use super::AnnounceError;
use super::Config;
use super::NodeAlias;
use super::SocketAddress;
Expand All @@ -469,7 +506,10 @@ mod tests {
fn node_announce_channel() {
// Default configuration with node alias and listening addresses unset
let mut node_config = Config::default();
assert!(!may_announce_channel(&node_config));
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingAliasAndAddresses)
);

// Set node alias with listening addresses unset
let alias_frm_str = |alias: &str| {
Expand All @@ -478,18 +518,33 @@ mod tests {
NodeAlias(bytes)
};
node_config.node_alias = Some(alias_frm_str("LDK_Node"));
assert!(!may_announce_channel(&node_config));
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingListeningAddresses)
);

// Set announcement addresses with listening addresses unset
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)
);

// Set node alias with an empty list of listening addresses
node_config.listening_addresses = Some(vec![]);
assert!(!may_announce_channel(&node_config));
assert_eq!(
may_announce_channel(&node_config),
Err(AnnounceError::MissingListeningAddresses)
);

// 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!(may_announce_channel(&node_config));
assert!(may_announce_channel(&node_config).is_ok());
}
}
30 changes: 14 additions & 16 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1077,23 +1077,21 @@ where
is_announced,
params: _,
} => {
if is_announced && !may_announce_channel(&*self.config) {
log_error!(
self.logger,
"Rejecting inbound announced channel from peer {} as not all required details are set. Please ensure node alias and listening addresses have been configured.",
counterparty_node_id,
);
if is_announced {
if let Err(err) = may_announce_channel(&*self.config) {
log_error!(self.logger, "Rejecting inbound announced channel from peer {} due to missing configuration: {}", counterparty_node_id, err);

self.channel_manager
.force_close_without_broadcasting_txn(
&temporary_channel_id,
&counterparty_node_id,
"Channel request rejected".to_string(),
)
.unwrap_or_else(|e| {
log_error!(self.logger, "Failed to reject channel: {:?}", e)
});
return Ok(());
self.channel_manager
.force_close_without_broadcasting_txn(
&temporary_channel_id,
&counterparty_node_id,
"Channel request rejected".to_string(),
)
.unwrap_or_else(|e| {
log_error!(self.logger, "Failed to reject channel: {:?}", e)
});
return Ok(());
}
}

let anchor_channel = channel_type.requires_anchors_zero_fee_htlc_tx();
Expand Down
38 changes: 24 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ impl Node {
let bcast_node_metrics = Arc::clone(&self.node_metrics);
let mut stop_bcast = self.stop_sender.subscribe();
let node_alias = self.config.node_alias.clone();
if may_announce_channel(&self.config) {
if may_announce_channel(&self.config).is_ok() {
runtime.spawn(async move {
// We check every 30 secs whether our last broadcast is NODE_ANN_BCAST_INTERVAL away.
#[cfg(not(test))]
Expand Down Expand Up @@ -457,8 +457,10 @@ impl Node {
continue;
}

let addresses = if let Some(addresses) = bcast_config.listening_addresses.clone() {
addresses
let addresses = if let Some(announcement_addresses) = bcast_config.announcement_addresses.clone() {
announcement_addresses
} else if let Some(listening_addresses) = bcast_config.listening_addresses.clone() {
listening_addresses
} else {
debug_assert!(false, "We checked whether the node may announce, so listening addresses should always be set");
Copy link
Contributor

Choose a reason for hiding this comment

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

this log will need to be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure it does because listening_addresses is the only thing that should always be set.

continue;
Expand Down Expand Up @@ -802,6 +804,14 @@ impl Node {
self.config.listening_addresses.clone()
}

/// Returns the addresses that the node will announce to the network.
pub fn announcement_addresses(&self) -> Option<Vec<SocketAddress>> {
self.config
.announcement_addresses
.clone()
.or_else(|| self.config.listening_addresses.clone())
}

/// Returns our node alias.
pub fn node_alias(&self) -> Option<NodeAlias> {
self.config.node_alias
Expand Down Expand Up @@ -1201,19 +1211,19 @@ impl Node {
&self, node_id: PublicKey, address: SocketAddress, channel_amount_sats: u64,
push_to_counterparty_msat: Option<u64>, channel_config: Option<ChannelConfig>,
) -> Result<UserChannelId, Error> {
if may_announce_channel(&self.config) {
self.open_channel_inner(
node_id,
address,
channel_amount_sats,
push_to_counterparty_msat,
channel_config,
true,
)
} else {
log_error!(self.logger, "Failed to open announced channel as the node hasn't been sufficiently configured to act as a forwarding node. Please make sure to configure listening addreesses and node alias");
if let Err(err) = may_announce_channel(&self.config) {
log_error!(self.logger, "Failed to open announced channel as the node hasn't been sufficiently configured to act as a forwarding node: {}", err);
return Err(Error::ChannelCreationFailed);
}

self.open_channel_inner(
node_id,
address,
channel_amount_sats,
push_to_counterparty_msat,
channel_config,
true,
)
}

/// Manually sync the LDK and BDK wallets with the current chain state and update the fee rate
Expand Down
Loading