diff --git a/iroh/examples/connect-unreliable.rs b/iroh/examples/connect-unreliable.rs index ea375ba0dd..f4d1278351 100644 --- a/iroh/examples/connect-unreliable.rs +++ b/iroh/examples/connect-unreliable.rs @@ -50,9 +50,11 @@ async fn main() -> anyhow::Result<()> { .bind() .await?; - let node_addr = endpoint.node_addr().await?; - let me = node_addr.node_id; + let me = endpoint.node_id(); println!("node id: {me}"); + endpoint.direct_addresses().initialized().await?; + endpoint.home_relay().initialized().await?; + let node_addr = endpoint.node_addr(); println!("node listening addresses:"); node_addr .direct_addresses diff --git a/iroh/examples/echo.rs b/iroh/examples/echo.rs index 5da09096f0..160950a130 100644 --- a/iroh/examples/echo.rs +++ b/iroh/examples/echo.rs @@ -23,7 +23,8 @@ const ALPN: &[u8] = b"iroh-example/echo/0"; #[tokio::main] async fn main() -> Result<()> { let router = accept_side().await?; - let node_addr = router.endpoint().node_addr().await?; + router.endpoint().node_addr_initialized().await; + let node_addr = router.endpoint().node_addr(); connect_side(node_addr).await?; diff --git a/iroh/examples/listen-unreliable.rs b/iroh/examples/listen-unreliable.rs index 745b20e5b0..64c3296ab9 100644 --- a/iroh/examples/listen-unreliable.rs +++ b/iroh/examples/listen-unreliable.rs @@ -35,7 +35,9 @@ async fn main() -> anyhow::Result<()> { println!("node id: {me}"); println!("node listening addresses:"); - let node_addr = endpoint.node_addr().await?; + endpoint.direct_addresses().initialized().await?; + endpoint.home_relay().initialized().await?; + let node_addr = endpoint.node_addr(); let local_addrs = node_addr .direct_addresses .into_iter() diff --git a/iroh/examples/listen.rs b/iroh/examples/listen.rs index 94950de210..98375d77f5 100644 --- a/iroh/examples/listen.rs +++ b/iroh/examples/listen.rs @@ -35,9 +35,11 @@ async fn main() -> anyhow::Result<()> { let me = endpoint.node_id(); println!("node id: {me}"); - println!("node listening addresses:"); - let node_addr = endpoint.node_addr().await?; + endpoint.direct_addresses().initialized().await?; + endpoint.home_relay().initialized().await?; + println!("node listening addresses:"); + let node_addr = endpoint.node_addr(); let local_addrs = node_addr .direct_addresses .into_iter() diff --git a/iroh/src/discovery.rs b/iroh/src/discovery.rs index 9bfe858185..638797bc0d 100644 --- a/iroh/src/discovery.rs +++ b/iroh/src/discovery.rs @@ -646,7 +646,7 @@ mod tests { }; let ep1_addr = NodeAddr::new(ep1.node_id()); // wait for our address to be updated and thus published at least once - ep1.node_addr().await?; + ep1.node_addr_initialized().await; let _conn = ep2.connect(ep1_addr, TEST_ALPN).await?; Ok(()) } @@ -672,7 +672,7 @@ mod tests { }; let ep1_addr = NodeAddr::new(ep1.node_id()); // wait for out address to be updated and thus published at least once - ep1.node_addr().await.context("waiting for NodeAddr")?; + ep1.node_addr_initialized().await; let _conn = ep2 .connect(ep1_addr, TEST_ALPN) .await @@ -704,7 +704,7 @@ mod tests { new_endpoint(secret, disco).await }; // wait for out address to be updated and thus published at least once - ep1.node_addr().await?; + ep1.node_addr_initialized().await; let _conn = ep2.connect(ep1.node_id(), TEST_ALPN).await?; Ok(()) } @@ -726,7 +726,7 @@ mod tests { new_endpoint(secret, disco).await }; // wait for out address to be updated and thus published at least once - ep1.node_addr().await?; + ep1.node_addr(); // 10x faster test via a 3s idle timeout instead of the 30s default let mut config = TransportConfig::default(); @@ -759,7 +759,7 @@ mod tests { new_endpoint(secret, disco).await }; // wait for out address to be updated and thus published at least once - ep1.node_addr().await?; + ep1.node_addr(); let ep1_wrong_addr = NodeAddr { node_id: ep1.node_id(), relay_url: None, diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 3654ad3fcb..209ca2034f 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -836,14 +836,66 @@ impl Endpoint { /// The returned [`NodeAddr`] will have the current [`RelayUrl`] and direct addresses /// as they would be returned by [`Endpoint::home_relay`] and /// [`Endpoint::direct_addresses`]. - pub async fn node_addr(&self) -> Result { - let addrs = self.direct_addresses().initialized().await?; - let relay = self.home_relay().get()?; - Ok(NodeAddr::from_parts( - self.node_id(), - relay, - addrs.into_iter().map(|x| x.addr), - )) + /// + /// Note that right after binding, the returned node address will likely be empty, because + /// both discovering our direct addresses and selecting our home relay takes some time. + /// + /// If you want to wait until a non-empty node address is available, you can await + /// [`Self::node_addr_initialized`] beforehand. + /// + /// ```no_run + /// # use iroh::{Endpoint, NodeAddr}; + /// # async fn wrapper() -> testresult::TestResult { + /// let ep = Endpoint::builder().bind().await?; + /// ep.node_addr_initialized().await; + /// let node_addr = ep.node_addr(); + /// assert!(!node_addr.is_empty()); + /// # Ok(()) + /// # } + /// ``` + /// + /// If you need an address with direct addresses, you can wait for those to be initialized instead: + /// + /// ```no_run + /// # use iroh::{Endpoint, NodeAddr}; + /// # async fn wrapper() -> testresult::TestResult { + /// let ep = Endpoint::builder().bind().await?; + /// ep.direct_addresses().initialized().await?; + /// let node_addr = ep.node_addr(); + /// assert!(!node_addr.direct_addresses.is_empty()); + /// # Ok(()) + /// # } + /// ``` + /// + /// Similarly, if you only care about the home relay, use `Endpoint::home_relay` and wait + /// for it to be initialized. + pub fn node_addr(&self) -> NodeAddr { + let addrs = self + .direct_addresses() + .get() + .expect("watchable gone, which is impossible") + .unwrap_or_default(); + let relay = self + .home_relay() + .get() + .expect("watchable gone, which is impossible"); + NodeAddr::from_parts(self.node_id(), relay, addrs.into_iter().map(|x| x.addr)) + } + + /// Wait for either the node's direct addresses or the node's home relay to be initialized. + /// + /// After awaiting this, [`Self::node_addr`] is guaranteed to return a non-empty node address. + /// + /// Under the hood, this races [`Watcher::initialized`] for both [`Self::direct_addresses`] and + /// [`Self::home_relay`]. + pub async fn node_addr_initialized(&self) { + // Wait for either the home relay or the direct addresses to be ready. + n0_future::future::race( + async { self.direct_addresses().initialized().await.map(|_| ()) }, + async { self.home_relay().initialized().await.map(|_| ()) }, + ) + .await + .expect("watchable gone, which is impossible"); } /// Returns a [`Watcher`] for the [`RelayUrl`] of the Relay server used as home relay. @@ -1907,7 +1959,7 @@ mod tests { .bind() .await .unwrap(); - let my_addr = ep.node_addr().await.unwrap(); + let my_addr = ep.node_addr(); let res = ep.connect(my_addr.clone(), TEST_ALPN).await; assert!(res.is_err()); let err = res.err().unwrap(); @@ -2184,8 +2236,10 @@ mod tests { .bind() .await .unwrap(); - let ep1_nodeaddr = ep1.node_addr().await.unwrap(); - let ep2_nodeaddr = ep2.node_addr().await.unwrap(); + ep1.node_addr_initialized().await; + ep2.node_addr_initialized().await; + let ep1_nodeaddr = ep1.node_addr(); + let ep2_nodeaddr = ep2.node_addr(); ep1.add_node_addr(ep2_nodeaddr.clone()).unwrap(); ep2.add_node_addr(ep1_nodeaddr.clone()).unwrap(); let ep1_nodeid = ep1.node_id(); @@ -2308,7 +2362,9 @@ mod tests { let ep1_nodeid = ep1.node_id(); let ep2_nodeid = ep2.node_id(); - let ep1_nodeaddr = ep1.node_addr().await.unwrap(); + // wait for the direct addresses to be initialized. + ep1.direct_addresses().initialized().await.unwrap(); + let ep1_nodeaddr = ep1.node_addr(); tracing::info!( "node id 1 {ep1_nodeid}, relay URL {:?}", ep1_nodeaddr.relay_url() @@ -2460,9 +2516,10 @@ mod tests { ) .await?; - connect_client_0rtt_expect_err(&client, server.node_addr().await?).await?; + server.node_addr_initialized().await; + connect_client_0rtt_expect_err(&client, server.node_addr()).await?; // The second 0rtt attempt should work - connect_client_0rtt_expect_ok(&client, server.node_addr().await?, true).await?; + connect_client_0rtt_expect_ok(&client, server.node_addr(), true).await?; client.close().await; server.close().await; @@ -2486,7 +2543,8 @@ mod tests { ) .await?; - connect_client_0rtt_expect_err(&client, server.node_addr().await?).await?; + server.node_addr_initialized().await; + connect_client_0rtt_expect_err(&client, server.node_addr()).await?; // connecting with another endpoint should not interfere with our // TLS session ticket cache for the first endpoint: @@ -2495,10 +2553,11 @@ mod tests { info_span!("another"), ) .await?; - connect_client_0rtt_expect_err(&client, another.node_addr().await?).await?; + another.node_addr_initialized().await; + connect_client_0rtt_expect_err(&client, another.node_addr()).await?; another.close().await; - connect_client_0rtt_expect_ok(&client, server.node_addr().await?, true).await?; + connect_client_0rtt_expect_ok(&client, server.node_addr(), true).await?; client.close().await; server.close().await; @@ -2517,8 +2576,9 @@ mod tests { let server_key = SecretKey::generate(rand::thread_rng()); let server = spawn_0rtt_server(server_key.clone(), info_span!("server-initial")).await?; - connect_client_0rtt_expect_err(&client, server.node_addr().await?).await?; - connect_client_0rtt_expect_ok(&client, server.node_addr().await?, true).await?; + server.node_addr_initialized().await; + connect_client_0rtt_expect_err(&client, server.node_addr()).await?; + connect_client_0rtt_expect_ok(&client, server.node_addr(), true).await?; server.close().await; @@ -2527,7 +2587,8 @@ mod tests { // we expect the client to *believe* it can 0-RTT connect to the server (hence expect_ok), // but the server will reject the early data because it discarded necessary state // to decrypt it when restarting. - connect_client_0rtt_expect_ok(&client, server.node_addr().await?, false).await?; + server.node_addr_initialized().await; + connect_client_0rtt_expect_ok(&client, server.node_addr(), false).await?; client.close().await; @@ -2542,7 +2603,8 @@ mod tests { .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await?; - let server_addr = server.node_addr().await?; + server.node_addr_initialized().await; + let server_addr = server.node_addr(); let server_task = tokio::spawn(async move { let incoming = server.accept().await.unwrap(); let conn = incoming.await?; diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index ee82ee9814..852424ed59 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -3503,10 +3503,7 @@ mod tests { })); println!("first conn!"); - let conn = m1 - .endpoint - .connect(m2.endpoint.node_addr().await?, ALPN) - .await?; + let conn = m1.endpoint.connect(m2.endpoint.node_addr(), ALPN).await?; println!("Closing first conn"); conn.close(0u32.into(), b"bye lolz"); conn.closed().await;