diff --git a/coordinator/src/bin/coordinator.rs b/coordinator/src/bin/coordinator.rs index eea084dfe..c57a02b91 100644 --- a/coordinator/src/bin/coordinator.rs +++ b/coordinator/src/bin/coordinator.rs @@ -38,6 +38,8 @@ const CLOSED_POSITION_SYNC_INTERVAL: Duration = Duration::from_secs(30); const UNREALIZED_PNL_SYNC_INTERVAL: Duration = Duration::from_secs(600); const CONNECTION_CHECK_INTERVAL: Duration = Duration::from_secs(30); +const NODE_ALIAS: &str = "10101.finance"; + #[tokio::main] async fn main() -> Result<()> { std::panic::set_hook( @@ -91,7 +93,7 @@ async fn main() -> Result<()> { let (node_event_sender, mut node_event_receiver) = watch::channel::>(None); let node = Arc::new(ln_dlc_node::node::Node::new_coordinator( - "10101.finance", + NODE_ALIAS, network, data_dir.as_path(), Arc::new(NodeStorage::new(pool.clone())), @@ -205,7 +207,14 @@ async fn main() -> Result<()> { connection::keep_public_channel_peers_connected(node.inner, CONNECTION_CHECK_INTERVAL) }); - let app = router(node, pool, settings, exporter); + let app = router( + node, + pool, + settings, + exporter, + opts.p2p_announcement_addresses(), + NODE_ALIAS, + ); // Start the metrics exporter autometrics::prometheus_exporter::init(); diff --git a/coordinator/src/routes.rs b/coordinator/src/routes.rs index 9ec13a734..914a5bac4 100644 --- a/coordinator/src/routes.rs +++ b/coordinator/src/routes.rs @@ -37,6 +37,9 @@ use coordinator_commons::TradeParams; use diesel::r2d2::ConnectionManager; use diesel::r2d2::Pool; use diesel::PgConnection; +use lightning::ln::msgs::NetAddress; +use ln_dlc_node::node::peer_manager::alias_as_bytes; +use ln_dlc_node::node::peer_manager::broadcast_node_announcement; use ln_dlc_node::node::NodeInfo; use opentelemetry_prometheus::PrometheusExporter; use orderbook_commons::OrderbookMsg; @@ -63,6 +66,8 @@ pub struct AppState { pub authenticated_users: Arc>>>, pub settings: RwLock, pub exporter: PrometheusExporter, + pub announcement_addresses: Vec, + pub node_alias: String, } pub fn router( @@ -70,6 +75,8 @@ pub fn router( pool: Pool>, settings: Settings, exporter: PrometheusExporter, + announcement_addresses: Vec, + node_alias: &str, ) -> Router { let (tx, _rx) = broadcast::channel(100); let app_state = Arc::new(AppState { @@ -79,6 +86,8 @@ pub fn router( tx_pricefeed: tx, authenticated_users: Default::default(), exporter, + announcement_addresses, + node_alias: node_alias.to_string(), }); Router::new() @@ -118,6 +127,10 @@ pub fn router( get(get_settings).put(update_settings), ) .route("/api/admin/sync", post(post_sync)) + .route( + "/api/admin/broadcast_announcement", + post(post_broadcast_announcement), + ) .route("/metrics", get(get_metrics)) .route("/health", get(get_health)) .with_state(app_state) @@ -238,6 +251,24 @@ pub async fn post_trade( Ok(invoice.to_string()) } +pub async fn post_broadcast_announcement( + State(state): State>, +) -> Result<(), AppError> { + let node_alias = alias_as_bytes(state.node_alias.as_str()).map_err(|e| { + AppError::InternalServerError(format!( + "Could not parse node alias {0} due to {e:#}", + state.node_alias + )) + })?; + broadcast_node_announcement( + &state.node.inner.peer_manager, + node_alias, + state.announcement_addresses.clone(), + ); + + Ok(()) +} + /// Internal API for syncing the wallet #[instrument(skip_all, err(Debug))] #[autometrics] diff --git a/crates/ln-dlc-node/src/node/mod.rs b/crates/ln-dlc-node/src/node/mod.rs index e56ca59b1..058bb8e8f 100644 --- a/crates/ln-dlc-node/src/node/mod.rs +++ b/crates/ln-dlc-node/src/node/mod.rs @@ -7,6 +7,7 @@ use crate::ln::EventHandler; use crate::ln::TracingLogger; use crate::ln_dlc_wallet::LnDlcWallet; use crate::node::dlc_channel::sub_channel_manager_periodic_check; +use crate::node::peer_manager::alias_as_bytes; use crate::node::peer_manager::broadcast_node_announcement; use crate::on_chain_wallet::OnChainWallet; use crate::seed::Bip39Seed; @@ -15,7 +16,6 @@ use crate::ChainMonitor; use crate::FakeChannelPaymentRequests; use crate::NetworkGraph; use crate::PeerManager; -use anyhow::ensure; use anyhow::Context; use anyhow::Result; use bitcoin::secp256k1::PublicKey; @@ -63,7 +63,7 @@ mod dlc_manager; pub(crate) mod invoice; mod ln_channel; mod oracle; -mod peer_manager; +pub mod peer_manager; mod storage; mod sub_channel_manager; mod wallet; @@ -85,7 +85,7 @@ pub use wallet::PaymentDetails; /// The interval at which the [`lightning::ln::msgs::NodeAnnouncement`] is broadcast. /// /// According to the LDK team, a value of up to 1 hour should be fine. -const BROADCAST_NODE_ANNOUNCEMENT_INTERVAL: Duration = Duration::from_secs(600); +const BROADCAST_NODE_ANNOUNCEMENT_INTERVAL: Duration = Duration::from_secs(3600); /// An LN-DLC node. pub struct Node { @@ -625,12 +625,11 @@ where }; let alias = alias_as_bytes(alias)?; - let node_announcement_interval = node_announcement_interval(network); let broadcast_node_announcement_handle = { let announcement_addresses = announcement_addresses; let peer_manager = peer_manager.clone(); let (fut, remote_handle) = async move { - let mut interval = tokio::time::interval(node_announcement_interval); + let mut interval = tokio::time::interval(BROADCAST_NODE_ANNOUNCEMENT_INTERVAL); loop { broadcast_node_announcement( &peer_manager, @@ -733,23 +732,3 @@ impl Display for NodeInfo { format!("{}@{}", self.pubkey, self.address).fmt(f) } } - -fn alias_as_bytes(alias: &str) -> Result<[u8; 32]> { - ensure!( - alias.len() <= 32, - "Node Alias can not be longer than 32 bytes" - ); - - let mut bytes = [0; 32]; - bytes[..alias.len()].copy_from_slice(alias.as_bytes()); - - Ok(bytes) -} - -fn node_announcement_interval(network: Network) -> Duration { - match network { - // We want to broadcast node announcements more frequently on regtest to make testing easier - Network::Regtest => Duration::from_secs(30), - _ => BROADCAST_NODE_ANNOUNCEMENT_INTERVAL, - } -} diff --git a/crates/ln-dlc-node/src/node/peer_manager.rs b/crates/ln-dlc-node/src/node/peer_manager.rs index 1a6943ead..52119237a 100644 --- a/crates/ln-dlc-node/src/node/peer_manager.rs +++ b/crates/ln-dlc-node/src/node/peer_manager.rs @@ -1,10 +1,11 @@ +use anyhow::ensure; use lightning::ln::msgs::NetAddress; use crate::PeerManager; const NODE_COLOR: [u8; 3] = [0; 3]; -pub(crate) fn broadcast_node_announcement( +pub fn broadcast_node_announcement( peer_manager: &PeerManager, alias: [u8; 32], inc_connection_addresses: Vec, @@ -13,3 +14,15 @@ pub(crate) fn broadcast_node_announcement( peer_manager.broadcast_node_announcement(NODE_COLOR, alias, inc_connection_addresses) } + +pub fn alias_as_bytes(alias: &str) -> anyhow::Result<[u8; 32]> { + ensure!( + alias.len() <= 32, + "Node Alias can not be longer than 32 bytes" + ); + + let mut bytes = [0; 32]; + bytes[..alias.len()].copy_from_slice(alias.as_bytes()); + + Ok(bytes) +} diff --git a/crates/tests-e2e/examples/fund.rs b/crates/tests-e2e/examples/fund.rs index b3a757e77..61e746f8b 100644 --- a/crates/tests-e2e/examples/fund.rs +++ b/crates/tests-e2e/examples/fund.rs @@ -6,6 +6,7 @@ use clap::Parser; use ln_dlc_node::node::NodeInfo; use local_ip_address::local_ip; use reqwest::Response; +use reqwest::StatusCode; use serde::Deserialize; use std::time::Duration; use tests_e2e::coordinator::Coordinator; @@ -39,6 +40,10 @@ async fn main() { } async fn fund_everything(faucet: &str, coordinator: &str) -> Result<()> { + // let node_info = get_node_info(faucet).await?; + // dbg!(node_info); + // return Ok(()); + let coordinator = Coordinator::new(init_reqwest(), coordinator); let coord_addr = coordinator.get_new_address().await?; fund(&coord_addr, Amount::ONE_BTC, faucet).await?; @@ -81,6 +86,27 @@ async fn fund_everything(faucet: &str, coordinator: &str) -> Result<()> { ) .await?; + // wait until channel has `peer_alias` set correctly + tracing::info!("Waiting until channel is has correct peer_alias set"); + let mut counter = 0; + loop { + if counter == 3 { + bail!("Could not verify channel is open. Please wipe and try again"); + } + counter += 1; + + let node_info = get_node_info(faucet).await?; + if let Some(node_info) = node_info { + if node_info.num_channels > 0 && node_info.node.alias == "10101.finance" { + break; + } + } + + tracing::info!("Manually broadcasting node announcement and waiting for a few seconds..."); + coordinator.broadcast_node_announcement().await?; + tokio::time::sleep(Duration::from_secs(5)).await; + } + let lnd_channels = get_text(&format!("{faucet}/lnd/v1/channels")).await?; tracing::info!("open LND channels: {}", lnd_channels); Ok(()) @@ -153,6 +179,40 @@ async fn post_query(path: &str, body: String, faucet: &str) -> Result Ok(response) } +async fn get_query(path: &str, faucet: &str) -> Result { + let faucet = faucet.to_string(); + let client = init_reqwest(); + let response = client.get(format!("{faucet}/{path}")).send().await?; + + Ok(response) +} + +#[derive(Deserialize, Debug, Clone)] +pub struct LndNodeInfo { + node: Node, + num_channels: u32, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Node { + alias: String, +} + +async fn get_node_info(faucet: &str) -> Result> { + let response = get_query( + "lnd/v1/graph/node/02dd6abec97f9a748bf76ad502b004ce05d1b2d1f43a9e76bd7d85e767ffb022c9", + faucet, + ) + .await?; + if response.status() == StatusCode::NOT_FOUND { + tracing::warn!("Node info not yet found."); + return Ok(None); + } + + let node_info = response.json().await?; + Ok(Some(node_info)) +} + /// Instructs lnd to open a public channel with the target node. /// 1. Connect to the target node. /// 2. Open channel to the target node. @@ -196,6 +256,7 @@ async fn open_channel(node_info: &NodeInfo, amount: Amount, faucet: &str) -> Res .await?; mine(10, faucet).await?; + tracing::info!("connected to channel"); tracing::info!("You can now use the lightning faucet {faucet}/faucet/"); diff --git a/crates/tests-e2e/src/coordinator.rs b/crates/tests-e2e/src/coordinator.rs index 85f710845..dddbf81fe 100644 --- a/crates/tests-e2e/src/coordinator.rs +++ b/crates/tests-e2e/src/coordinator.rs @@ -94,6 +94,14 @@ impl Coordinator { .context("could not parse json") } + pub async fn broadcast_node_announcement(&self) -> Result { + let status = self + .post("/api/admin/broadcast_announcement") + .await? + .error_for_status()?; + Ok(status) + } + async fn get(&self, path: &str) -> Result { self.client .get(format!("{0}{path}", self.host))