From 3a29a41562a2388c46b61227fef89317bd9bf2d8 Mon Sep 17 00:00:00 2001 From: Roland Sherwin Date: Fri, 24 Oct 2025 11:40:17 +0530 Subject: [PATCH 1/2] feat(bootstrap): split Bootstrap::new method into two for easy integration - Split the `async fn Bootstrap::new` method into two methods, where the `new` is made a "sync" method and `async fn new_with_preloaded_addrs` retains the same functionality as the old new method. - The `new_with_preloaded_addrs` method is only required inside clients which tries to pre-populate its RT before starting the libp2p driver. - Introduces `is_addr_queue_empty`, as it will be used by the reachability check implementation - Deprecate `try_next_dial_addr` as it is just a copy of `next_addr` --- ant-bootstrap/src/bootstrap.rs | 101 ++++++++++++++-------- ant-bootstrap/src/cache_store/mod.rs | 4 +- ant-bootstrap/src/contacts_fetcher.rs | 40 ++------- ant-bootstrap/src/lib.rs | 28 +++--- ant-node-manager/src/cmd/nat_detection.rs | 5 +- ant-node/src/bin/antnode/main.rs | 2 +- ant-node/src/python.rs | 1 - ant-node/src/spawn/node_spawner.rs | 2 +- autonomi/src/client/mod.rs | 7 +- 9 files changed, 96 insertions(+), 94 deletions(-) diff --git a/ant-bootstrap/src/bootstrap.rs b/ant-bootstrap/src/bootstrap.rs index 9d48f1a41c..5ebf65446b 100644 --- a/ant-bootstrap/src/bootstrap.rs +++ b/ant-bootstrap/src/bootstrap.rs @@ -79,7 +79,9 @@ pub struct Bootstrap { } impl Bootstrap { - pub async fn new(mut config: BootstrapConfig) -> Result { + /// Create a new Bootstrap manager with the given configuration. + /// Use `new_with_preloaded_addrs` to ensure that the struct contains at least MIN_INITIAL_ADDRS addresses immediately. + pub fn new(config: BootstrapConfig) -> Result { let contacts_progress = Self::build_contacts_progress(&config)?; let mut addrs_queue = VecDeque::new(); @@ -93,8 +95,8 @@ impl Bootstrap { info!("Skipping ANT_PEERS environment variable as per configuration"); } - for addr in config.initial_peers.drain(..) { - if let Some(addr) = craft_valid_multiaddr(&addr, false) { + for addr in config.initial_peers.iter() { + if let Some(addr) = craft_valid_multiaddr(addr) { info!("Adding addr from arguments: {addr}"); Self::push_addr(&mut addrs_queue, &mut bootstrap_peer_ids, addr); } else { @@ -117,7 +119,7 @@ impl Bootstrap { let cache_store = BootstrapCacheStore::new(config.clone())?; let mut bootstrap = Self { - cache_store, + cache_store: cache_store.clone(), addrs: addrs_queue, cache_pending, contacts_progress, @@ -135,11 +137,22 @@ impl Bootstrap { bootstrap.cache_task = Some(cache_task); if config.first { - info!("First node in network; clearing any existing cache"); - bootstrap.cache_store.write().await?; - return Ok(bootstrap); + tokio::spawn(async move { + if let Err(err) = cache_store.write().await { + error!("Failed to clear bootstrap cache for first node: {err}"); + } else { + info!("Bootstrap cache cleared for first node"); + } + }); } + Ok(bootstrap) + } + + /// Create a new Bootstrap manager and ensure it has at least MIN_INITIAL_ADDRS addresses preloaded. + pub async fn new_with_preloaded_addrs(config: BootstrapConfig) -> Result { + let mut bootstrap = Self::new(config)?; + // ensure the initial queue is not empty by fetching from cache/contacts if needed // // not required for 'first' node @@ -250,6 +263,29 @@ impl Bootstrap { } } + /// Check if the address queue is empty without consuming an address. + /// Returns `Some(true)` if empty, `Some(false)` if not empty, and `None` if still fetching or has cache or contacts + /// sources pending. + pub fn is_addr_queue_empty(&self) -> Option { + if !self.addrs.is_empty() { + return Some(false); + } + + if self.fetch_in_progress.is_some() { + return None; + } + + if self.cache_pending { + return None; + } + + if self.contacts_progress.is_some() { + return None; + } + + Some(true) + } + fn process_events(&mut self) { while let Ok(event) = self.event_rx.try_recv() { match event { @@ -306,18 +342,6 @@ impl Bootstrap { } } - fn try_next_dial_addr(&mut self) -> Result> { - match self.next_addr() { - Ok(Some(addr)) => Ok(Some(addr)), - Ok(None) => Ok(None), - Err(Error::NoBootstrapPeersFound) => { - self.bootstrap_completed = true; - Err(Error::NoBootstrapPeersFound) - } - Err(err) => Err(err), - } - } - /// Return true if the bootstrapping process has completed or if we have run out of addresses, otherwise false. fn has_bootstrap_completed(&self, contacted_peers: usize) -> bool { if self.bootstrap_completed { @@ -370,7 +394,7 @@ impl Bootstrap { } while self.ongoing_dials.len() < self.cache_store.config().max_concurrent_dials { - match self.try_next_dial_addr() { + match self.next_addr() { Ok(Some(mut addr)) => { let addr_clone = addr.clone(); let peer_id = Self::pop_p2p(&mut addr); @@ -438,6 +462,7 @@ impl Bootstrap { } Err(Error::NoBootstrapPeersFound) => { info!("No more bootstrap peers available to dial."); + self.bootstrap_completed = true; break; } Err(err) => { @@ -644,7 +669,7 @@ impl Bootstrap { // Read from ANT_PEERS environment variable if present if let Ok(addrs) = std::env::var(ANT_PEERS_ENV) { for addr_str in addrs.split(',') { - if let Some(addr) = craft_valid_multiaddr_from_str(addr_str, false) { + if let Some(addr) = craft_valid_multiaddr_from_str(addr_str) { info!("Adding addr from environment variable: {addr}"); bootstrap_addresses.push(addr); } else { @@ -825,7 +850,7 @@ mod tests { ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let first_two = vec![ expect_next_addr(&mut flow).await.unwrap(), @@ -891,7 +916,7 @@ mod tests { let mut config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let got = expect_next_addr(&mut flow).await.unwrap(); assert_eq!(got, cache_addr); @@ -929,7 +954,7 @@ mod tests { let mut config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let err = expect_err(&mut flow).await; assert!(matches!(err, Error::NoBootstrapPeersFound)); @@ -979,7 +1004,7 @@ mod tests { let mut config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let first = expect_next_addr(&mut flow).await.unwrap(); assert_eq!(first, contact_one); @@ -1088,7 +1113,7 @@ mod tests { ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let initial_results = vec![ expect_next_addr(&mut flow).await.unwrap(), @@ -1145,7 +1170,7 @@ mod tests { BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config).await; + let result = Bootstrap::new(config); assert!( result.is_err(), "Should error when env peers are disabled and no other sources available" @@ -1181,7 +1206,7 @@ mod tests { config.disable_env_peers = true; config.disable_cache_reading = true; - let result = Bootstrap::new(config).await; + let result = Bootstrap::new(config); assert!( result.is_err(), "Should error when cache reading is disabled and no other sources available" @@ -1199,7 +1224,7 @@ mod tests { ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let flow = Bootstrap::new(config).await.unwrap(); + let flow = Bootstrap::new(config).unwrap(); assert!( flow.has_terminated(), @@ -1215,7 +1240,7 @@ mod tests { ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let flow = Bootstrap::new(config).await.unwrap(); + let flow = Bootstrap::new(config).unwrap(); assert!( !flow.has_terminated(), @@ -1244,7 +1269,7 @@ mod tests { ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let flow = Bootstrap::new(config).await.unwrap(); + let flow = Bootstrap::new(config).unwrap(); assert!( flow.is_bootstrap_peer(&env_peer_id), @@ -1280,7 +1305,7 @@ mod tests { config.initial_peers.push(invalid_addr); - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let first = expect_next_addr(&mut flow).await.unwrap(); assert_eq!(first, valid_addr, "Should get the valid address"); @@ -1321,7 +1346,7 @@ mod tests { config.disable_env_peers = true; let addr_from_config = config.initial_peers[0].clone(); - let mut flow = Bootstrap::new(config).await.unwrap(); + let mut flow = Bootstrap::new(config).unwrap(); let first = expect_next_addr(&mut flow).await.unwrap(); assert_eq!( @@ -1358,7 +1383,7 @@ mod tests { BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config).await; + let result = Bootstrap::new(config); assert!( result.is_err(), @@ -1401,7 +1426,7 @@ mod tests { ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let _flow = Bootstrap::new(config).await.unwrap(); + let _flow = Bootstrap::new(config).unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; @@ -1438,7 +1463,7 @@ mod tests { BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config).await; + let result = Bootstrap::new(config); assert!( result.is_ok(), @@ -1486,7 +1511,7 @@ mod tests { BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config).await; + let result = Bootstrap::new(config); assert!( result.is_ok(), "Should succeed with few contacts (< 50 but > 0)" @@ -1532,7 +1557,7 @@ mod tests { BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config).await; + let result = Bootstrap::new(config); assert!( result.is_err(), diff --git a/ant-bootstrap/src/cache_store/mod.rs b/ant-bootstrap/src/cache_store/mod.rs index 8c9f7194fe..f6e1a41c67 100644 --- a/ant-bootstrap/src/cache_store/mod.rs +++ b/ant-bootstrap/src/cache_store/mod.rs @@ -81,7 +81,7 @@ impl BootstrapCacheStore { if addr.iter().any(|p| matches!(p, Protocol::P2pCircuit)) { return; } - let Some(addr) = craft_valid_multiaddr(&addr, false) else { + let Some(addr) = craft_valid_multiaddr(&addr) else { return; }; let peer_id = match addr.iter().find(|p| matches!(p, Protocol::P2p(_))) { @@ -149,7 +149,7 @@ impl BootstrapCacheStore { } if self.data.read().await.peers.is_empty() { - info!("Cache is empty, skipping sync and flush to disk"); + info!("No peers to write to disk, skipping sync to disk"); return Ok(()); } diff --git a/ant-bootstrap/src/contacts_fetcher.rs b/ant-bootstrap/src/contacts_fetcher.rs index 8c8f41cc68..264615fcda 100644 --- a/ant-bootstrap/src/contacts_fetcher.rs +++ b/ant-bootstrap/src/contacts_fetcher.rs @@ -48,8 +48,6 @@ pub struct ContactsFetcher { endpoints: Vec, /// Reqwest Client request_client: Client, - /// Ignore PeerId in the multiaddr if not present. This is only useful for fetching nat detection contacts - ignore_peer_id: bool, } impl ContactsFetcher { @@ -68,7 +66,6 @@ impl ContactsFetcher { max_addrs: usize::MAX, endpoints, request_client, - ignore_peer_id: false, }) } @@ -105,10 +102,6 @@ impl ContactsFetcher { self.endpoints.push(endpoint); } - pub fn ignore_peer_id(&mut self, ignore_peer_id: bool) { - self.ignore_peer_id = ignore_peer_id; - } - /// Fetch the list of bootstrap multiaddrs from all configured endpoints pub async fn fetch_bootstrap_addresses(&self) -> Result> { info!( @@ -125,12 +118,7 @@ impl ContactsFetcher { endpoint ); ( - Self::fetch_from_endpoint( - self.request_client.clone(), - &endpoint, - self.ignore_peer_id, - ) - .await, + Self::fetch_from_endpoint(self.request_client.clone(), &endpoint).await, endpoint, ) }) @@ -175,11 +163,7 @@ impl ContactsFetcher { } /// Fetch the list of multiaddrs from a single endpoint - async fn fetch_from_endpoint( - request_client: Client, - endpoint: &Url, - ignore_peer_id: bool, - ) -> Result> { + async fn fetch_from_endpoint(request_client: Client, endpoint: &Url) -> Result> { let mut retries = 0; let bootstrap_addresses = loop { @@ -194,7 +178,7 @@ impl ContactsFetcher { if response.status().is_success() { let text = response.text().await?; - match Self::try_parse_response(&text, ignore_peer_id) { + match Self::try_parse_response(&text) { Ok(addrs) => break addrs, Err(err) => { warn!("Failed to parse response with err: {err:?}"); @@ -239,7 +223,7 @@ impl ContactsFetcher { } /// Try to parse a response from an endpoint - fn try_parse_response(response: &str, ignore_peer_id: bool) -> Result> { + fn try_parse_response(response: &str) -> Result> { let cache_data = if let Ok(data) = serde_json::from_str::(response) { @@ -282,7 +266,7 @@ impl ContactsFetcher { let bootstrap_addresses = response .split('\n') - .filter_map(|str| craft_valid_multiaddr_from_str(str, ignore_peer_id)) + .filter_map(craft_valid_multiaddr_from_str) .collect::>(); if bootstrap_addresses.is_empty() { @@ -508,16 +492,12 @@ mod tests { } #[tokio::test] - async fn test_invalid_multiaddr() { + async fn test_mutliaddr_without_peerid() { let mock_server = MockServer::start().await; Mock::given(method("GET")) .and(path("/")) - .respond_with( - ResponseTemplate::new(200).set_body_string( - "/ip4/127.0.0.1/tcp/8080\n/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5", - ), - ) + .respond_with(ResponseTemplate::new(200).set_body_string("/ip4/127.0.0.1/tcp/8080")) .mount(&mock_server) .await; @@ -525,10 +505,8 @@ mod tests { fetcher.endpoints = vec![mock_server.uri().parse().unwrap()]; let addrs = fetcher.fetch_bootstrap_addresses().await.unwrap(); - let valid_addr: Multiaddr = - "/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" - .parse() - .unwrap(); + + let valid_addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); assert_eq!(addrs[0], valid_addr); } diff --git a/ant-bootstrap/src/lib.rs b/ant-bootstrap/src/lib.rs index b2e7ab50e5..2da2ad8f78 100644 --- a/ant-bootstrap/src/lib.rs +++ b/ant-bootstrap/src/lib.rs @@ -47,8 +47,8 @@ pub const ANT_PEERS_ENV: &str = "ANT_PEERS"; /// Craft a proper address to avoid any ill formed addresses /// -/// ignore_peer_id is only used for nat-detection contact list -pub fn craft_valid_multiaddr(addr: &Multiaddr, ignore_peer_id: bool) -> Option { +/// PeerId is optional, if not present, it will be ignored. +pub fn craft_valid_multiaddr(addr: &Multiaddr) -> Option { let peer_id = addr .iter() .find(|protocol| matches!(protocol, Protocol::P2p(_))); @@ -91,20 +91,20 @@ pub fn craft_valid_multiaddr(addr: &Multiaddr, ignore_peer_id: bool) -> Option Option { +/// Craft a proper address to avoid any ill formed addresses +/// +/// PeerId is optional, if not present, it will be ignored. +pub fn craft_valid_multiaddr_from_str(addr_str: &str) -> Option { let Ok(addr) = addr_str.parse::() else { warn!("Failed to parse multiaddr from str {addr_str}"); return None; }; - craft_valid_multiaddr(&addr, ignore_peer_id) + craft_valid_multiaddr(&addr) } pub fn multiaddr_get_peer_id(addr: &Multiaddr) -> Option { @@ -151,7 +151,7 @@ mod tests { for (addr, should_be_valid) in variants { let parsed: Multiaddr = addr.parse().unwrap(); - let result = craft_valid_multiaddr(&parsed, false); + let result = craft_valid_multiaddr(&parsed); if should_be_valid { let crafted = result.unwrap_or_else(|| panic!("Expected valid multiaddr: {addr}")); @@ -165,21 +165,21 @@ mod tests { #[test] fn test_craft_valid_multiaddr_from_str() { let valid = "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE"; - assert!(craft_valid_multiaddr_from_str(valid, false).is_some()); + assert!(craft_valid_multiaddr_from_str(valid).is_some()); let invalid = "not a multiaddr"; - assert!(craft_valid_multiaddr_from_str(invalid, false).is_none()); + assert!(craft_valid_multiaddr_from_str(invalid).is_none()); let missing_peer = "/ip4/127.0.0.1/tcp/8080"; - assert!(craft_valid_multiaddr_from_str(missing_peer, false).is_none()); - assert!(craft_valid_multiaddr_from_str(missing_peer, true).is_some()); + assert!(craft_valid_multiaddr_from_str(missing_peer).is_none()); + assert!(craft_valid_multiaddr_from_str(missing_peer).is_some()); } #[test] fn test_craft_valid_multiaddr_ignore_peer_id() { let addr_without_peer: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap(); - assert!(craft_valid_multiaddr(&addr_without_peer, false).is_none()); - assert!(craft_valid_multiaddr(&addr_without_peer, true).is_some()); + assert!(craft_valid_multiaddr(&addr_without_peer).is_none()); + assert!(craft_valid_multiaddr(&addr_without_peer).is_some()); } #[test] diff --git a/ant-node-manager/src/cmd/nat_detection.rs b/ant-node-manager/src/cmd/nat_detection.rs index 82998a7b52..7ad4943a96 100644 --- a/ant-node-manager/src/cmd/nat_detection.rs +++ b/ant-node-manager/src/cmd/nat_detection.rs @@ -37,9 +37,8 @@ pub async fn run_nat_detection( let servers = match servers { Some(servers) => servers, None => { - let mut contacts_fetcher = ContactsFetcher::new()?; - contacts_fetcher.ignore_peer_id(true); - contacts_fetcher.insert_endpoint(NAT_DETECTION_SERVERS_LIST_URL.parse()?); + let contacts_fetcher = + ContactsFetcher::with_endpoints(vec![NAT_DETECTION_SERVERS_LIST_URL.parse()?])?; let fetched = contacts_fetcher.fetch_bootstrap_addresses().await?; fetched diff --git a/ant-node/src/bin/antnode/main.rs b/ant-node/src/bin/antnode/main.rs index 4ddae3c0f9..2359ba8be6 100644 --- a/ant-node/src/bin/antnode/main.rs +++ b/ant-node/src/bin/antnode/main.rs @@ -300,7 +300,7 @@ fn main() -> Result<()> { let bootstrap_config = BootstrapConfig::try_from(&opt.peers)? .with_backwards_compatible_writes(opt.write_older_cache_files); - let bootstrap = rt.block_on(Bootstrap::new(bootstrap_config))?; + let bootstrap = Bootstrap::new(bootstrap_config)?; let msg = format!( "Running {} v{}", diff --git a/ant-node/src/python.rs b/ant-node/src/python.rs index 79ec8ca9f1..20f74bc046 100644 --- a/ant-node/src/python.rs +++ b/ant-node/src/python.rs @@ -90,7 +90,6 @@ impl PyAntNode { local, ..Default::default() }) - .await .map_err(|e| PyRuntimeError::new_err(format!("Failed to initialise bootstrap: {e}")))?; let mut node_builder = NodeBuilder::new( diff --git a/ant-node/src/spawn/node_spawner.rs b/ant-node/src/spawn/node_spawner.rs index 3edee1e6c3..b36e29b292 100644 --- a/ant-node/src/spawn/node_spawner.rs +++ b/ant-node/src/spawn/node_spawner.rs @@ -139,7 +139,7 @@ async fn spawn_node( let bootstrap_config = bootstrap_config.unwrap_or_default(); let local = bootstrap_config.local; - let bootstrap = Bootstrap::new(bootstrap_config).await?; + let bootstrap = Bootstrap::new(bootstrap_config)?; let mut node_builder = NodeBuilder::new( keypair, diff --git a/autonomi/src/client/mod.rs b/autonomi/src/client/mod.rs index 865f4cfcb5..dc21cf41b7 100644 --- a/autonomi/src/client/mod.rs +++ b/autonomi/src/client/mod.rs @@ -270,7 +270,8 @@ impl Client { ant_protocol::version::set_network_id(network_id); } - let bootstrap = Bootstrap::new(config.bootstrap_config.clone()).await?; + let bootstrap = + Bootstrap::new_with_preloaded_addrs(config.bootstrap_config.clone()).await?; let network = Network::new(bootstrap)?; // Wait for the network to be ready with enough peers @@ -290,7 +291,7 @@ impl Client { }; // Retry the bootstrap and connection with cache disabled - let bootstrap_retry = Bootstrap::new(retry_config).await?; + let bootstrap_retry = Bootstrap::new_with_preloaded_addrs(retry_config).await?; let network_retry = Network::new(bootstrap_retry)?; // Wait for connectivity with the new bootstrap configuration @@ -388,7 +389,7 @@ mod tests { .parse() .unwrap(), ]; - let bootstrap = Bootstrap::new( + let bootstrap = Bootstrap::new_with_preloaded_addrs( BootstrapConfig::default() .with_initial_peers(initial_peers) .with_disable_cache_reading(true) From c4f5a73bb8ea61711640ce7bec07492d3e66cc3e Mon Sep 17 00:00:00 2001 From: Roland Sherwin Date: Tue, 28 Oct 2025 08:40:34 +0530 Subject: [PATCH 2/2] feat(bootstrap): revamp the tests to make it much more detailed --- ant-bootstrap/src/bootstrap.rs | 809 +++++++++++++------------- ant-bootstrap/src/contacts_fetcher.rs | 20 +- ant-bootstrap/src/lib.rs | 206 ++++--- ant-node/src/bin/antnode/main.rs | 2 +- 4 files changed, 547 insertions(+), 490 deletions(-) diff --git a/ant-bootstrap/src/bootstrap.rs b/ant-bootstrap/src/bootstrap.rs index 5ebf65446b..eb1204342d 100644 --- a/ant-bootstrap/src/bootstrap.rs +++ b/ant-bootstrap/src/bootstrap.rs @@ -81,6 +81,8 @@ pub struct Bootstrap { impl Bootstrap { /// Create a new Bootstrap manager with the given configuration. /// Use `new_with_preloaded_addrs` to ensure that the struct contains at least MIN_INITIAL_ADDRS addresses immediately. + /// + /// Must be called from a tokio runtime context. pub fn new(config: BootstrapConfig) -> Result { let contacts_progress = Self::build_contacts_progress(&config)?; @@ -819,55 +821,123 @@ mod tests { } } - fn generate_valid_test_multiaddr(ip_third: u8, ip_fourth: u8, port: u16) -> Multiaddr { - let peer_id = libp2p::PeerId::random(); - format!("/ip4/10.{ip_third}.{ip_fourth}.1/tcp/{port}/p2p/{peer_id}") - .parse() - .unwrap() - } - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_cli_arguments_precedence() { + async fn address_sources_follow_priority_env_cli_cache_contacts() { + let _env_guard = env_lock().await; + remove_env_var(ANT_PEERS_ENV); + let env_addr: Multiaddr = - "/ip4/10.0.0.1/tcp/1200/p2p/12D3KooWQnE7zXkVUEGBnJtNfR88Ujz4ezgm6bVnkvxHCzhF7S5S" + "/ip4/10.1.0.1/udp/1300/quic-v1/p2p/12D3KooWBbtXX6gY5xPD7NzNGGbj2428NJQ4HNvvBnSE5g4R7Pkf" .parse() .unwrap(); let cli_addr: Multiaddr = - "/ip4/10.0.0.2/tcp/1201/p2p/12D3KooWQx2TSK7g1C8x3QK7gBqdqbQEkd6vDT7Pxu5gb1xmgjvp" + "/ip4/10.1.0.2/udp/1301/quic-v1/p2p/12D3KooWCRfYwq9c3PAXo5cTp3snq72Knqukcec4c9qT1AMyvMPd" .parse() .unwrap(); - - let _env_guard = env_lock().await; set_env_var(ANT_PEERS_ENV, &env_addr.to_string()); + let cache_addr_one: Multiaddr = + "/ip4/10.1.0.3/udp/1302/quic-v1/p2p/12D3KooWMmKJcWUP9UqP4g1n3LH1htkvSUStn1aQGQxGc1dQcYxA" + .parse() + .unwrap(); + let cache_addr_two: Multiaddr = + "/ip4/10.1.0.4/udp/1303/quic-v1/p2p/12D3KooWA4b4T6Dz4RUtqnYDEBt3eGkqRykGGBqBP3ZiZsaAJ2jp" + .parse() + .unwrap(); + let temp_dir = TempDir::new().unwrap(); + let file_name = BootstrapCacheStore::cache_file_name(false); + let mut cache_data = CacheData::default(); + cache_data.add_peer( + multiaddr_get_peer_id(&cache_addr_one).unwrap(), + std::iter::once(&cache_addr_one), + 3, + 10, + ); + cache_data.add_peer( + multiaddr_get_peer_id(&cache_addr_two).unwrap(), + std::iter::once(&cache_addr_two), + 3, + 10, + ); + cache_data + .write_to_file(temp_dir.path(), &file_name) + .unwrap(); + + let mock_server = MockServer::start().await; + let contact_one: Multiaddr = + "/ip4/10.1.0.5/udp/1304/quic-v1/p2p/12D3KooWQGyiCWkmKvgFVF1PsvBLnBxG29BAsoAhH4m6qjUpBAk1" + .parse() + .unwrap(); + let contact_two: Multiaddr = + "/ip4/10.1.0.6/udp/1305/quic-v1/p2p/12D3KooWGpMibW82dManEXZDV4SSQSSHqzTeWY5Avzkdx6yrosNG" + .parse() + .unwrap(); + + Mock::given(method("GET")) + .and(path("/contacts_one")) + .respond_with(ResponseTemplate::new(200).set_body_string(contact_one.to_string())) + .expect(1) + .mount(&mock_server) + .await; + + Mock::given(method("GET")) + .and(path("/contacts_two")) + .respond_with(ResponseTemplate::new(200).set_body_string(contact_two.to_string())) + .expect(1) + .mount(&mock_server) + .await; let config = InitialPeersConfig { - ignore_cache: true, - local: true, - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), addrs: vec![cli_addr.clone()], + bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + network_contacts_url: vec![ + format!("{}/contacts_one", mock_server.uri()), + format!("{}/contacts_two", mock_server.uri()), + ], ..Default::default() }; let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); let mut flow = Bootstrap::new(config).unwrap(); - let first_two = vec![ + let initial_results = vec![ + expect_next_addr(&mut flow).await.unwrap(), + expect_next_addr(&mut flow).await.unwrap(), + ]; + let initial_set: HashSet<_> = initial_results.into_iter().collect(); + let expected_initial: HashSet<_> = + [env_addr.clone(), cli_addr.clone()].into_iter().collect(); + assert_eq!(initial_set, expected_initial); + + let cache_results = vec![ expect_next_addr(&mut flow).await.unwrap(), expect_next_addr(&mut flow).await.unwrap(), ]; - let first_set: HashSet<_> = first_two.into_iter().collect(); - let expected: HashSet<_> = [env_addr.clone(), cli_addr.clone()].into_iter().collect(); - assert_eq!(first_set, expected); + let cache_set: HashSet<_> = cache_results.into_iter().collect(); + let expected_cache: HashSet<_> = [cache_addr_one.clone(), cache_addr_two.clone()] + .into_iter() + .collect(); + assert_eq!(cache_set, expected_cache); + + let contact_first = expect_next_addr(&mut flow).await.unwrap(); + assert_eq!(contact_first, contact_one); + + let contact_second = expect_next_addr(&mut flow).await.unwrap(); + assert_eq!(contact_second, contact_two); let err = expect_err(&mut flow).await; assert!(matches!(err, Error::NoBootstrapPeersFound)); + let requests = mock_server.received_requests().await.unwrap(); + assert_eq!(requests.len(), 2); + assert_eq!(requests[0].url.path(), "/contacts_one"); + assert_eq!(requests[1].url.path(), "/contacts_two"); + remove_env_var(ANT_PEERS_ENV); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_env_variable_parsing() { + async fn env_variable_parses_comma_separated_multiaddrs() { let _env_guard = env_lock().await; set_env_var( ANT_PEERS_ENV, @@ -882,29 +952,32 @@ mod tests { let parsed_set: std::collections::HashSet<_> = parsed.into_iter().map(|addr| addr.to_string()).collect(); let expected = std::collections::HashSet::from([ - "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE" - .to_string(), - "/ip4/127.0.0.2/udp/8081/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" - .to_string(), - ]); + "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE" + .to_string(), + "/ip4/127.0.0.2/udp/8081/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" + .to_string(), + ]); assert_eq!(parsed_set, expected); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn loads_addresses_from_cache_when_initial_queue_is_empty() { + async fn cache_loads_varying_quantities_zero_few_many() { let _env_guard = env_lock().await; - let cache_addr: Multiaddr = - "/ip4/127.0.0.1/tcp/1202/p2p/12D3KooWKGt8umjJQ4sDzFXo2UcHBaF33rqmFcWtXM6nbryL5G4J" - .parse() - .unwrap(); - let peer_id = multiaddr_get_peer_id(&cache_addr).unwrap(); - let temp_dir = TempDir::new().unwrap(); let file_name = BootstrapCacheStore::cache_file_name(true); - let mut cache_data = CacheData::default(); - cache_data.add_peer(peer_id, std::iter::once(&cache_addr), 3, 10); - cache_data + let mut cache_data_one = CacheData::default(); + let cache_addr_one: Multiaddr = + "/ip4/127.0.0.1/tcp/1202/p2p/12D3KooWKGt8umjJQ4sDzFXo2UcHBaF33rqmFcWtXM6nbryL5G4J" + .parse() + .unwrap(); + cache_data_one.add_peer( + multiaddr_get_peer_id(&cache_addr_one).unwrap(), + std::iter::once(&cache_addr_one), + 3, + 10, + ); + cache_data_one .write_to_file(temp_dir.path(), &file_name) .unwrap(); @@ -919,65 +992,111 @@ mod tests { let mut flow = Bootstrap::new(config).unwrap(); let got = expect_next_addr(&mut flow).await.unwrap(); - assert_eq!(got, cache_addr); + assert_eq!(got, cache_addr_one); let err = expect_err(&mut flow).await; assert!(matches!(err, Error::NoBootstrapPeersFound)); - } - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_first_flag_behavior() { - let _env_guard = env_lock().await; + let temp_dir_few = TempDir::new().unwrap(); + let mut cache_data_few = CacheData::default(); + for i in 0..MIN_INITIAL_ADDRS - 2 { + let peer_id = libp2p::PeerId::random(); + let addr: Multiaddr = + format!("/ip4/10.4.{}.1/udp/{}/quic-v1/p2p/{peer_id}", i, 4000 + i) + .parse() + .unwrap(); + cache_data_few.add_peer(peer_id, std::iter::once(&addr), 3, 10); + } + cache_data_few + .write_to_file(temp_dir_few.path(), &file_name) + .unwrap(); - let mock_server = MockServer::start().await; - Mock::given(method("GET")) - .and(path("/peers")) - .respond_with(ResponseTemplate::new(200).set_body_string( - "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", - )) - .expect(0) - .mount(&mock_server) - .await; + let config_few = InitialPeersConfig { + local: true, + bootstrap_cache_dir: Some(temp_dir_few.path().to_path_buf()), + ..Default::default() + }; + let mut config_few = + BootstrapConfig::try_from(&config_few).expect("Failed to create BootstrapConfig"); + config_few.disable_env_peers = true; - let temp_dir = TempDir::new().unwrap(); - let config = InitialPeersConfig { - first: true, - addrs: vec![ - "/ip4/127.0.0.2/udp/8081/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" + let result_few = Bootstrap::new_with_preloaded_addrs(config_few).await; + assert!( + result_few.is_ok(), + "Should succeed with few contacts (< 5 but > 0)" + ); + + let mut flow_few = result_few.unwrap(); + let mut count = 0; + while let Ok(Some(_addr)) = flow_few.next_addr() { + count += 1; + if count >= MIN_INITIAL_ADDRS { + break; + } + } + assert_eq!( + count, + MIN_INITIAL_ADDRS - 2, + "Should have exactly 3 contacts" + ); + + let temp_dir_many = TempDir::new().unwrap(); + let mut cache_data_many = CacheData::default(); + for i in 0..MIN_INITIAL_ADDRS + 1 { + let peer_id = libp2p::PeerId::random(); + let addr: Multiaddr = + format!("/ip4/10.3.{}.1/udp/{}/quic-v1/p2p/{peer_id}", i, 3000 + i) .parse() - .unwrap(), - ], - network_contacts_url: vec![format!("{}/peers", mock_server.uri())], - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - ..Default::default() - }; - let mut config = - BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - config.disable_env_peers = true; - let mut flow = Bootstrap::new(config).unwrap(); + .unwrap(); + cache_data_many.add_peer(peer_id, std::iter::once(&addr), 3, 10); + } + cache_data_many + .write_to_file(temp_dir_many.path(), &file_name) + .unwrap(); - let err = expect_err(&mut flow).await; - assert!(matches!(err, Error::NoBootstrapPeersFound)); + let config_many = InitialPeersConfig { + local: true, + bootstrap_cache_dir: Some(temp_dir_many.path().to_path_buf()), + ..Default::default() + }; + let mut config_many = + BootstrapConfig::try_from(&config_many).expect("Failed to create BootstrapConfig"); + config_many.disable_env_peers = true; + + let result_many = Bootstrap::new_with_preloaded_addrs(config_many).await; assert!( - mock_server.received_requests().await.unwrap().is_empty(), - "first flag should prevent contact fetches" + result_many.is_ok(), + "Should successfully initialize with many contacts in cache" + ); + + let mut flow_many = result_many.unwrap(); + let mut count_many = 0; + while let Ok(Some(_addr)) = flow_many.next_addr() { + count_many += 1; + if count_many >= MIN_INITIAL_ADDRS { + break; + } + } + assert!( + count_many > 0, + "Should have loaded contacts from cache, got {count_many}" ); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_multiple_network_contacts() { + async fn contacts_fetched_sequentially_from_multiple_endpoints() { let _env_guard = env_lock().await; let mock_server = MockServer::start().await; let contact_one: Multiaddr = - "/ip4/192.168.0.1/tcp/1203/p2p/12D3KooWPULWT1qXJ1jzYVtQocvKXgcv6U7Pp3ui3EB7mN8hXAsP" - .parse() - .unwrap(); + "/ip4/192.168.0.1/udp/1203/quic-v1/p2p/12D3KooWPULWT1qXJ1jzYVtQocvKXgcv6U7Pp3ui3EB7mN8hXAsP" + .parse() + .unwrap(); let contact_two: Multiaddr = - "/ip4/192.168.0.2/tcp/1204/p2p/12D3KooWPsMPaEjaWjW6GWpAne6LYcwBQEJfnDbhQFNs6ytzmBn5" - .parse() - .unwrap(); + "/ip4/192.168.0.2/udp/1204/quic-v1/p2p/12D3KooWPsMPaEjaWjW6GWpAne6LYcwBQEJfnDbhQFNs6ytzmBn5" + .parse() + .unwrap(); Mock::given(method("GET")) .and(path("/first")) @@ -1022,138 +1141,85 @@ mod tests { } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_full_bootstrap_flow() { + async fn first_node_skips_sources_clears_cache_and_terminates() { let _env_guard = env_lock().await; - remove_env_var(ANT_PEERS_ENV); - - let env_addr: Multiaddr = - "/ip4/10.1.0.1/tcp/1300/p2p/12D3KooWBbtXX6gY5xPD7NzNGGbj2428NJQ4HNvvBnSE5g4R7Pkf" - .parse() - .unwrap(); - let cli_addr: Multiaddr = - "/ip4/10.1.0.2/tcp/1301/p2p/12D3KooWCRfYwq9c3PAXo5cTp3snq72Knqukcec4c9qT1AMyvMPd" - .parse() - .unwrap(); - set_env_var(ANT_PEERS_ENV, &env_addr.to_string()); - let cache_addr_one: Multiaddr = - "/ip4/10.1.0.3/tcp/1302/p2p/12D3KooWMmKJcWUP9UqP4g1n3LH1htkvSUStn1aQGQxGc1dQcYxA" - .parse() - .unwrap(); - let cache_addr_two: Multiaddr = - "/ip4/10.1.0.4/tcp/1303/p2p/12D3KooWA4b4T6Dz4RUtqnYDEBt3eGkqRykGGBqBP3ZiZsaAJ2jp" - .parse() - .unwrap(); + let peer_id = libp2p::PeerId::random(); + let cache_addr: Multiaddr = format!("/ip4/10.2.0.1/udp/2007/quic-v1/p2p/{peer_id}") + .parse() + .unwrap(); let temp_dir = TempDir::new().unwrap(); let file_name = BootstrapCacheStore::cache_file_name(false); + let mut cache_data = CacheData::default(); - cache_data.add_peer( - multiaddr_get_peer_id(&cache_addr_one).unwrap(), - std::iter::once(&cache_addr_one), - 3, - 10, - ); - cache_data.add_peer( - multiaddr_get_peer_id(&cache_addr_two).unwrap(), - std::iter::once(&cache_addr_two), - 3, - 10, - ); + cache_data.add_peer(peer_id, std::iter::once(&cache_addr), 3, 10); cache_data .write_to_file(temp_dir.path(), &file_name) .unwrap(); - let mock_server = MockServer::start().await; - let contact_one: Multiaddr = - "/ip4/10.1.0.5/tcp/1304/p2p/12D3KooWQGyiCWkmKvgFVF1PsvBLnBxG29BAsoAhH4m6qjUpBAk1" - .parse() - .unwrap(); - let contact_two: Multiaddr = - "/ip4/10.1.0.6/tcp/1305/p2p/12D3KooWGpMibW82dManEXZDV4SSQSSHqzTeWY5Avzkdx6yrosNG" - .parse() - .unwrap(); - - Mock::given(method("GET")) - .and(path("/contacts_one")) - .respond_with(ResponseTemplate::new(200).set_body_string(contact_one.to_string())) - .expect(1) - .mount(&mock_server) - .await; - - Mock::given(method("GET")) - .and(path("/contacts_two")) - .respond_with(ResponseTemplate::new(200).set_body_string(contact_two.to_string())) - .expect(1) - .mount(&mock_server) - .await; - let file_path = temp_dir.path().join(format!( "version_{}/{}", CacheData::CACHE_DATA_VERSION, file_name )); - let contents = std::fs::read_to_string(&file_path).unwrap(); - assert!(contents.contains(&cache_addr_one.to_string())); - assert!(contents.contains(&cache_addr_two.to_string())); - assert_eq!( - Bootstrap::fetch_from_env(), - vec![env_addr.clone()], - "environment variable should yield the configured address" + let contents_before = std::fs::read_to_string(&file_path).unwrap(); + assert!( + contents_before.contains(&cache_addr.to_string()), + "Cache should contain the address before initialization" ); + let mock_server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/peers")) + .respond_with(ResponseTemplate::new(200).set_body_string( + "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", + )) + .expect(0) + .mount(&mock_server) + .await; + let config = InitialPeersConfig { - addrs: vec![cli_addr.clone()], - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - network_contacts_url: vec![ - format!("{}/contacts_one", mock_server.uri()), - format!("{}/contacts_two", mock_server.uri()), + first: true, + addrs: vec![ + "/ip4/127.0.0.2/udp/8081/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" + .parse() + .unwrap(), ], + network_contacts_url: vec![format!("{}/peers", mock_server.uri())], + bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), ..Default::default() }; - let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); + let mut config = + BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); + config.disable_env_peers = true; let mut flow = Bootstrap::new(config).unwrap(); - let initial_results = vec![ - expect_next_addr(&mut flow).await.unwrap(), - expect_next_addr(&mut flow).await.unwrap(), - ]; - let initial_set: HashSet<_> = initial_results.into_iter().collect(); - let expected_initial: HashSet<_> = - [env_addr.clone(), cli_addr.clone()].into_iter().collect(); - assert_eq!(initial_set, expected_initial); + let err = expect_err(&mut flow).await; + assert!(matches!(err, Error::NoBootstrapPeersFound)); + assert!( + mock_server.received_requests().await.unwrap().is_empty(), + "first flag should prevent contact fetches" + ); - let cache_results = vec![ - expect_next_addr(&mut flow).await.unwrap(), - expect_next_addr(&mut flow).await.unwrap(), - ]; - let cache_set: HashSet<_> = cache_results.into_iter().collect(); - let expected_cache: HashSet<_> = [cache_addr_one.clone(), cache_addr_two.clone()] - .into_iter() - .collect(); - assert_eq!(cache_set, expected_cache); + assert!(flow.has_terminated()); - let contact_first = expect_next_addr(&mut flow).await.unwrap(); - assert_eq!(contact_first, contact_one); - - let contact_second = expect_next_addr(&mut flow).await.unwrap(); - assert_eq!(contact_second, contact_two); - - let err = expect_err(&mut flow).await; - assert!(matches!(err, Error::NoBootstrapPeersFound)); - - let requests = mock_server.received_requests().await.unwrap(); - assert_eq!(requests.len(), 2); - assert_eq!(requests[0].url.path(), "/contacts_one"); - assert_eq!(requests[1].url.path(), "/contacts_two"); + tokio::time::sleep(Duration::from_millis(100)).await; - remove_env_var(ANT_PEERS_ENV); + let contents_after = std::fs::read_to_string(&file_path).unwrap(); + assert!( + !contents_after.contains(&cache_addr.to_string()), + "Cache should be cleared for first node" + ); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_disable_env_peers_flag() { - let env_addr = generate_valid_test_multiaddr(2, 0, 2000); + async fn preload_respects_disabled_source_flags() { + let peer_id = libp2p::PeerId::random(); + let env_addr: Multiaddr = format!("/ip4/10.2.0.1/udp/2000/quic-v1/p2p/{peer_id}") + .parse() + .unwrap(); let _env_guard = env_lock().await; set_env_var(ANT_PEERS_ENV, &env_addr.to_string()); @@ -1170,155 +1236,78 @@ mod tests { BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config); + let result = Bootstrap::new_with_preloaded_addrs(config).await; assert!( result.is_err(), "Should error when env peers are disabled and no other sources available" ); assert!(matches!(result.unwrap_err(), Error::NoBootstrapPeersFound)); - remove_env_var(ANT_PEERS_ENV); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_disable_cache_reading_flag() { - let _env_guard = env_lock().await; - - let cache_addr = generate_valid_test_multiaddr(2, 0, 2001); - let peer_id = multiaddr_get_peer_id(&cache_addr).unwrap(); + let peer_id2 = libp2p::PeerId::random(); + let cache_addr: Multiaddr = format!("/ip4/10.2.0.1/udp/2001/quic-v1/p2p/{peer_id2}") + .parse() + .unwrap(); - let temp_dir = TempDir::new().unwrap(); + let temp_dir2 = TempDir::new().unwrap(); let file_name = BootstrapCacheStore::cache_file_name(true); let mut cache_data = CacheData::default(); - cache_data.add_peer(peer_id, std::iter::once(&cache_addr), 3, 10); + cache_data.add_peer(peer_id2, std::iter::once(&cache_addr), 3, 10); cache_data - .write_to_file(temp_dir.path(), &file_name) + .write_to_file(temp_dir2.path(), &file_name) .unwrap(); - let config = InitialPeersConfig { + let config2 = InitialPeersConfig { local: true, - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + bootstrap_cache_dir: Some(temp_dir2.path().to_path_buf()), ..Default::default() }; - let mut config = - BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - config.disable_env_peers = true; - config.disable_cache_reading = true; + let mut config2 = + BootstrapConfig::try_from(&config2).expect("Failed to create BootstrapConfig"); + config2.disable_env_peers = true; + config2.disable_cache_reading = true; - let result = Bootstrap::new(config); + let result2 = Bootstrap::new_with_preloaded_addrs(config2).await; assert!( - result.is_err(), + result2.is_err(), "Should error when cache reading is disabled and no other sources available" ); - assert!(matches!(result.unwrap_err(), Error::NoBootstrapPeersFound)); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_bootstrap_completed_initialization() { - let temp_dir = TempDir::new().unwrap(); - - let config = InitialPeersConfig { - first: true, - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - ..Default::default() - }; - let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let flow = Bootstrap::new(config).unwrap(); - - assert!( - flow.has_terminated(), - "bootstrap_completed should be true for first node" - ); - - let config = InitialPeersConfig { - first: false, - local: true, - ignore_cache: true, - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - addrs: vec![generate_valid_test_multiaddr(2, 0, 2002)], - ..Default::default() - }; - let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let flow = Bootstrap::new(config).unwrap(); - - assert!( - !flow.has_terminated(), - "bootstrap_completed should be false for non-first node" - ); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_bootstrap_peer_ids_population() { - let env_addr = generate_valid_test_multiaddr(2, 0, 2003); - let cli_addr = generate_valid_test_multiaddr(2, 0, 2004); - - let env_peer_id = multiaddr_get_peer_id(&env_addr).unwrap(); - let cli_peer_id = multiaddr_get_peer_id(&cli_addr).unwrap(); - - let _env_guard = env_lock().await; - set_env_var(ANT_PEERS_ENV, &env_addr.to_string()); - - let temp_dir = TempDir::new().unwrap(); - - let config = InitialPeersConfig { - local: true, - ignore_cache: true, - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - addrs: vec![cli_addr.clone()], - ..Default::default() - }; - let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let flow = Bootstrap::new(config).unwrap(); - - assert!( - flow.is_bootstrap_peer(&env_peer_id), - "Peer ID from env should be tracked" - ); - assert!( - flow.is_bootstrap_peer(&cli_peer_id), - "Peer ID from CLI should be tracked" - ); + assert!(matches!(result2.unwrap_err(), Error::NoBootstrapPeersFound)); remove_env_var(ANT_PEERS_ENV); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_invalid_multiaddr_in_initial_peers() { + async fn preload_timeout_when_no_addresses_available() { let _env_guard = env_lock().await; - let valid_addr = generate_valid_test_multiaddr(2, 0, 2005); - let invalid_addr: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse().unwrap(); - let temp_dir = TempDir::new().unwrap(); + let file_name = BootstrapCacheStore::cache_file_name(true); + let cache_data = CacheData::default(); + cache_data + .write_to_file(temp_dir.path(), &file_name) + .unwrap(); let config = InitialPeersConfig { local: true, - ignore_cache: true, bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - addrs: vec![valid_addr.clone()], ..Default::default() }; let mut config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - config.initial_peers.push(invalid_addr); - - let mut flow = Bootstrap::new(config).unwrap(); - - let first = expect_next_addr(&mut flow).await.unwrap(); - assert_eq!(first, valid_addr, "Should get the valid address"); + let result = Bootstrap::new_with_preloaded_addrs(config).await; - let err = expect_err(&mut flow).await; assert!( - matches!(err, Error::NoBootstrapPeersFound), - "Should not find any more peers after valid one (invalid addr was filtered)" + result.is_err(), + "Should error when no addresses are available from any source" ); + assert!(matches!(result.unwrap_err(), Error::NoBootstrapPeersFound)); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_local_network_skips_contacts() { + async fn contacts_disabled_by_local_flag() { let _env_guard = env_lock().await; let mock_server = MockServer::start().await; @@ -1333,12 +1322,17 @@ mod tests { let temp_dir = TempDir::new().unwrap(); + let peer_id = libp2p::PeerId::random(); + let addr: Multiaddr = format!("/ip4/10.2.0.1/udp/2006/quic-v1/p2p/{peer_id}") + .parse() + .unwrap(); + let config = InitialPeersConfig { local: true, ignore_cache: true, bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), network_contacts_url: vec![format!("{}/should-not-be-called", mock_server.uri())], - addrs: vec![generate_valid_test_multiaddr(2, 0, 2006)], + addrs: vec![addr], ..Default::default() }; let mut config = @@ -1346,7 +1340,7 @@ mod tests { config.disable_env_peers = true; let addr_from_config = config.initial_peers[0].clone(); - let mut flow = Bootstrap::new(config).unwrap(); + let mut flow = Bootstrap::new_with_preloaded_addrs(config).await.unwrap(); let first = expect_next_addr(&mut flow).await.unwrap(); assert_eq!( @@ -1364,205 +1358,224 @@ mod tests { } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_timeout_with_no_addresses() { + async fn contacts_fetch_failures_handled_gracefully() { let _env_guard = env_lock().await; let temp_dir = TempDir::new().unwrap(); - let file_name = BootstrapCacheStore::cache_file_name(true); + let file_name = BootstrapCacheStore::cache_file_name(false); let cache_data = CacheData::default(); cache_data .write_to_file(temp_dir.path(), &file_name) .unwrap(); + let mock_server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/failing-endpoint")) + .respond_with(ResponseTemplate::new(500)) + .expect(1..) + .mount(&mock_server) + .await; + let config = InitialPeersConfig { - local: true, bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + network_contacts_url: vec![format!("{}/failing-endpoint", mock_server.uri())], ..Default::default() }; let mut config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config); + let result = Bootstrap::new_with_preloaded_addrs(config).await; assert!( result.is_err(), - "Should error when no addresses are available from any source" + "Should error when all sources fail and no contacts are available" ); assert!(matches!(result.unwrap_err(), Error::NoBootstrapPeersFound)); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_first_node_clears_cache() { + async fn invalid_multiaddrs_filtered_from_initial_peers() { let _env_guard = env_lock().await; - let cache_addr = generate_valid_test_multiaddr(2, 0, 2007); - let peer_id = multiaddr_get_peer_id(&cache_addr).unwrap(); - - let temp_dir = TempDir::new().unwrap(); - let file_name = BootstrapCacheStore::cache_file_name(false); - - let mut cache_data = CacheData::default(); - cache_data.add_peer(peer_id, std::iter::once(&cache_addr), 3, 10); - cache_data - .write_to_file(temp_dir.path(), &file_name) + let peer_id = libp2p::PeerId::random(); + let valid_addr: Multiaddr = format!("/ip4/10.2.0.1/udp/2005/quic-v1/p2p/{peer_id}") + .parse() .unwrap(); + let invalid_addr: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse().unwrap(); - let file_path = temp_dir.path().join(format!( - "version_{}/{}", - CacheData::CACHE_DATA_VERSION, - file_name - )); - - let contents_before = std::fs::read_to_string(&file_path).unwrap(); - assert!( - contents_before.contains(&cache_addr.to_string()), - "Cache should contain the address before initialization" - ); + let temp_dir = TempDir::new().unwrap(); let config = InitialPeersConfig { - first: true, + local: true, + ignore_cache: true, bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + addrs: vec![valid_addr.clone()], ..Default::default() }; - let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - let _flow = Bootstrap::new(config).unwrap(); + let mut config = + BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); + config.disable_env_peers = true; - tokio::time::sleep(Duration::from_millis(100)).await; + config.initial_peers.push(invalid_addr); - let contents_after = std::fs::read_to_string(&file_path).unwrap(); + let mut flow = Bootstrap::new_with_preloaded_addrs(config).await.unwrap(); + + let first = expect_next_addr(&mut flow).await.unwrap(); + assert_eq!(first, valid_addr, "Should get the valid address"); + + let err = expect_err(&mut flow).await; assert!( - !contents_after.contains(&cache_addr.to_string()), - "Cache should be cleared for first node" + matches!(err, Error::NoBootstrapPeersFound), + "Should not find any more peers after valid one (invalid addr was filtered)" ); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_new_loads_at_least_50_contacts() { + async fn bootstrap_peer_ids_tracked_from_all_sources() { + let env_peer_id = libp2p::PeerId::random(); + let cli_peer_id = libp2p::PeerId::random(); + let env_addr: Multiaddr = format!("/ip4/10.2.0.1/udp/2003/quic-v1/p2p/{env_peer_id}") + .parse() + .unwrap(); + let cli_addr: Multiaddr = format!("/ip4/10.2.0.2/udp/2004/quic-v1/p2p/{cli_peer_id}") + .parse() + .unwrap(); + let _env_guard = env_lock().await; + set_env_var(ANT_PEERS_ENV, &env_addr.to_string()); let temp_dir = TempDir::new().unwrap(); - let file_name = BootstrapCacheStore::cache_file_name(true); - - let mut cache_data = CacheData::default(); - for i in 0..60 { - let addr = generate_valid_test_multiaddr(3, i as u8, 3000 + i); - let peer_id = multiaddr_get_peer_id(&addr).unwrap(); - cache_data.add_peer(peer_id, std::iter::once(&addr), 3, 10); - } - cache_data - .write_to_file(temp_dir.path(), &file_name) - .unwrap(); let config = InitialPeersConfig { local: true, + ignore_cache: true, bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + addrs: vec![cli_addr.clone()], ..Default::default() }; - let mut config = - BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - config.disable_env_peers = true; - - let result = Bootstrap::new(config); + let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); + let flow = Bootstrap::new_with_preloaded_addrs(config).await.unwrap(); assert!( - result.is_ok(), - "Should successfully initialize with 60 contacts in cache" + flow.is_bootstrap_peer(&env_peer_id), + "Peer ID from env should be tracked" ); - - let mut flow = result.unwrap(); - let mut count = 0; - while let Ok(Some(_addr)) = flow.next_addr() { - count += 1; - if count >= 60 { - break; - } - } - assert!( - count > 0, - "Should have loaded contacts from cache, got {count}" + flow.is_bootstrap_peer(&cli_peer_id), + "Peer ID from CLI should be tracked" ); + + let random_peer = libp2p::PeerId::random(); + assert!(!flow.is_bootstrap_peer(&random_peer)); + + remove_env_var(ANT_PEERS_ENV); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_new_succeeds_with_few_contacts() { + async fn queue_empty_returns_correct_state_based_on_pending_fetches() { + let temp_dir = TempDir::new().unwrap(); + let peer_id = libp2p::PeerId::random(); + let addr: Multiaddr = format!("/ip4/127.0.0.1/udp/5000/quic-v1/p2p/{peer_id}") + .parse() + .unwrap(); + let _env_guard = env_lock().await; + set_env_var(ANT_PEERS_ENV, &addr.to_string()); - let temp_dir = TempDir::new().unwrap(); + let config = InitialPeersConfig { + local: true, + ignore_cache: true, + bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + ..Default::default() + }; + let config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); + + let flow = Bootstrap::new(config).unwrap(); + + assert_eq!(flow.is_addr_queue_empty(), Some(false)); + + remove_env_var(ANT_PEERS_ENV); + + let temp_dir_empty = TempDir::new().unwrap(); + + let config_empty = InitialPeersConfig { + local: true, + ignore_cache: true, + bootstrap_cache_dir: Some(temp_dir_empty.path().to_path_buf()), + ..Default::default() + }; + let mut config_empty = + BootstrapConfig::try_from(&config_empty).expect("Failed to create BootstrapConfig"); + config_empty.disable_env_peers = true; + + let flow_empty = Bootstrap::new(config_empty).unwrap(); + + assert_eq!(flow_empty.is_addr_queue_empty(), Some(true)); + + let peer_id_cache = libp2p::PeerId::random(); + let cache_addr: Multiaddr = format!("/ip4/10.5.1.1/udp/5001/quic-v1/p2p/{peer_id_cache}") + .parse() + .unwrap(); + + let temp_dir_pending = TempDir::new().unwrap(); let file_name = BootstrapCacheStore::cache_file_name(true); let mut cache_data = CacheData::default(); - for i in 0..5 { - let addr = generate_valid_test_multiaddr(4, i as u8, 4000 + i); - let peer_id = multiaddr_get_peer_id(&addr).unwrap(); - cache_data.add_peer(peer_id, std::iter::once(&addr), 3, 10); - } + cache_data.add_peer(peer_id_cache, std::iter::once(&cache_addr), 3, 10); cache_data - .write_to_file(temp_dir.path(), &file_name) + .write_to_file(temp_dir_pending.path(), &file_name) .unwrap(); - let config = InitialPeersConfig { + let config_pending = InitialPeersConfig { local: true, - bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), + bootstrap_cache_dir: Some(temp_dir_pending.path().to_path_buf()), ..Default::default() }; - let mut config = - BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); - config.disable_env_peers = true; - - let result = Bootstrap::new(config); - assert!( - result.is_ok(), - "Should succeed with few contacts (< 50 but > 0)" - ); + let mut config_pending = + BootstrapConfig::try_from(&config_pending).expect("Failed to create BootstrapConfig"); + config_pending.disable_env_peers = true; - let mut flow = result.unwrap(); - let mut count = 0; - while let Ok(Some(_addr)) = flow.next_addr() { - count += 1; - if count >= 10 { - break; - } - } + let flow_pending = Bootstrap::new(config_pending).unwrap(); - assert_eq!(count, 5, "Should have exactly 5 contacts"); + assert_eq!(flow_pending.is_addr_queue_empty(), None); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_new_errors_with_zero_contacts() { - let _env_guard = env_lock().await; + async fn terminated_false_until_completion_criteria_met() { + let temp_dir_first = TempDir::new().unwrap(); + + let config_first = InitialPeersConfig { + first: true, + bootstrap_cache_dir: Some(temp_dir_first.path().to_path_buf()), + ..Default::default() + }; + let config_first = + BootstrapConfig::try_from(&config_first).expect("Failed to create BootstrapConfig"); + + let flow_first = Bootstrap::new(config_first).unwrap(); + + assert!(flow_first.has_terminated()); let temp_dir = TempDir::new().unwrap(); - let file_name = BootstrapCacheStore::cache_file_name(false); - let cache_data = CacheData::default(); - cache_data - .write_to_file(temp_dir.path(), &file_name) + let peer_id = libp2p::PeerId::random(); + let addr: Multiaddr = format!("/ip4/10.5.4.1/udp/5004/quic-v1/p2p/{peer_id}") + .parse() .unwrap(); - let mock_server = MockServer::start().await; - Mock::given(method("GET")) - .and(path("/failing-endpoint")) - .respond_with(ResponseTemplate::new(500)) - .expect(1..) - .mount(&mock_server) - .await; - let config = InitialPeersConfig { + local: true, + ignore_cache: true, bootstrap_cache_dir: Some(temp_dir.path().to_path_buf()), - network_contacts_url: vec![format!("{}/failing-endpoint", mock_server.uri())], + addrs: vec![addr], ..Default::default() }; let mut config = BootstrapConfig::try_from(&config).expect("Failed to create BootstrapConfig"); config.disable_env_peers = true; - let result = Bootstrap::new(config); + let flow = Bootstrap::new(config).unwrap(); - assert!( - result.is_err(), - "Should error when all sources fail and no contacts are available" - ); - assert!(matches!(result.unwrap_err(), Error::NoBootstrapPeersFound)); + assert!(!flow.has_terminated()); } } diff --git a/ant-bootstrap/src/contacts_fetcher.rs b/ant-bootstrap/src/contacts_fetcher.rs index 264615fcda..70cd3b7ff4 100644 --- a/ant-bootstrap/src/contacts_fetcher.rs +++ b/ant-bootstrap/src/contacts_fetcher.rs @@ -402,7 +402,7 @@ mod tests { .and(path("/")) .respond_with( ResponseTemplate::new(200) - .set_body_string("/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE\n/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"), + .set_body_string("/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE\n/ip4/127.0.0.2/udp/8080/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5"), ) .mount(&mock_server) .await; @@ -414,11 +414,11 @@ mod tests { assert_eq!(addrs.len(), 2); let addr1: Multiaddr = - "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE" + "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE" .parse() .unwrap(); let addr2: Multiaddr = - "/ip4/127.0.0.2/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" + "/ip4/127.0.0.2/udp/8080/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" .parse() .unwrap(); assert!(addrs.iter().any(|p| p == &addr1)); @@ -441,7 +441,7 @@ mod tests { Mock::given(method("GET")) .and(path("/")) .respond_with(ResponseTemplate::new(200).set_body_string( - "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5", + "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5", )) .mount(&mock_server2) .await; @@ -456,7 +456,7 @@ mod tests { assert_eq!(addrs.len(), 1); let addr: Multiaddr = - "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" + "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" .parse() .unwrap(); assert_eq!(addrs[0], addr); @@ -497,7 +497,9 @@ mod tests { Mock::given(method("GET")) .and(path("/")) - .respond_with(ResponseTemplate::new(200).set_body_string("/ip4/127.0.0.1/tcp/8080")) + .respond_with( + ResponseTemplate::new(200).set_body_string("/ip4/127.0.0.1/udp/8080/quic-v1"), + ) .mount(&mock_server) .await; @@ -506,7 +508,7 @@ mod tests { let addrs = fetcher.fetch_bootstrap_addresses().await.unwrap(); - let valid_addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + let valid_addr: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap(); assert_eq!(addrs[0], valid_addr); } @@ -535,7 +537,7 @@ mod tests { Mock::given(method("GET")) .and(path("/")) .respond_with( - ResponseTemplate::new(200).set_body_string("\n \n/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5\n \n"), + ResponseTemplate::new(200).set_body_string("\n \n/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5\n \n"), ) .mount(&mock_server) .await; @@ -547,7 +549,7 @@ mod tests { assert_eq!(addrs.len(), 1); let addr: Multiaddr = - "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" + "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWD2aV1f3qkhggzEFaJ24CEFYkSdZF5RKoMLpU6CwExYV5" .parse() .unwrap(); assert_eq!(addrs[0], addr); diff --git a/ant-bootstrap/src/lib.rs b/ant-bootstrap/src/lib.rs index 2da2ad8f78..76560efb78 100644 --- a/ant-bootstrap/src/lib.rs +++ b/ant-bootstrap/src/lib.rs @@ -62,32 +62,13 @@ pub fn craft_valid_multiaddr(addr: &Multiaddr) -> Option { let udp = addr .iter() - .find(|protocol| matches!(protocol, Protocol::Udp(_))); - let tcp = addr + .find(|protocol| matches!(protocol, Protocol::Udp(_)))?; + + output_address.push(udp); + let quic = addr .iter() - .find(|protocol| matches!(protocol, Protocol::Tcp(_))); - - // UDP or TCP - if let Some(udp) = udp { - output_address.push(udp); - if let Some(quic) = addr - .iter() - .find(|protocol| matches!(protocol, Protocol::QuicV1)) - { - output_address.push(quic); - } - } else if let Some(tcp) = tcp { - output_address.push(tcp); - - if let Some(ws) = addr - .iter() - .find(|protocol| matches!(protocol, Protocol::Ws(_))) - { - output_address.push(ws); - } - } else { - return None; - } + .find(|protocol| matches!(protocol, Protocol::QuicV1))?; + output_address.push(quic); if let Some(peer_id) = peer_id { output_address.push(peer_id); @@ -123,74 +104,135 @@ mod tests { use super::*; use libp2p::Multiaddr; + const VALID_PEER_ID: &str = "12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE"; + + #[test] + fn craft_valid_multiaddr_accepts_udp_quic_v1_with_peer_id() { + let input = format!("/ip4/127.0.0.1/udp/8080/quic-v1/p2p/{VALID_PEER_ID}"); + let addr: Multiaddr = input.parse().unwrap(); + + let result = craft_valid_multiaddr(&addr).expect("should accept udp/quic-v1 with peer id"); + + assert_eq!(result.to_string(), input); + } + + #[test] + fn craft_valid_multiaddr_accepts_udp_quic_v1_without_peer_id() { + let input = "/ip4/127.0.0.1/udp/8080/quic-v1"; + let addr: Multiaddr = input.parse().unwrap(); + + let result = + craft_valid_multiaddr(&addr).expect("should accept udp/quic-v1 without peer id"); + + assert_eq!(result.to_string(), input); + } + + #[test] + fn craft_valid_multiaddr_rejects_tcp_transport() { + let input = format!("/ip4/127.0.0.1/tcp/8080/p2p/{VALID_PEER_ID}"); + let addr: Multiaddr = input.parse().unwrap(); + + let result = craft_valid_multiaddr(&addr); + + assert!(result.is_none(), "should reject tcp transport"); + } + + #[test] + fn craft_valid_multiaddr_rejects_tcp_with_websocket() { + let input = format!("/ip4/127.0.0.1/tcp/8080/ws/p2p/{VALID_PEER_ID}"); + let addr: Multiaddr = input.parse().unwrap(); + + let result = craft_valid_multiaddr(&addr); + + assert!(result.is_none(), "should reject tcp/ws transport"); + } + + #[test] + fn craft_valid_multiaddr_rejects_udp_without_quic() { + let input = "/ip4/127.0.0.1/udp/8080"; + let addr: Multiaddr = input.parse().unwrap(); + + let result = craft_valid_multiaddr(&addr); + + assert!(result.is_none(), "should reject udp without quic-v1"); + } + + #[test] + fn craft_valid_multiaddr_rejects_address_without_udp() { + let input = format!("/ip4/127.0.0.1/p2p/{VALID_PEER_ID}"); + let addr: Multiaddr = input.parse().unwrap(); + + let result = craft_valid_multiaddr(&addr); + + assert!(result.is_none(), "should reject address without udp"); + } + #[test] - fn test_transport_protocol_variants() { - let variants = [ - ( - "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", - true, - ), - ( - "/ip4/127.0.0.1/tcp/8080/ws/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", - true, - ), - ( - "/ip4/127.0.0.1/tcp/8080/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", - true, - ), - ("/ip4/127.0.0.1/tcp/8080", false), - ( - "/ip4/127.0.0.1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", - false, - ), - ( - "/ip4/127.0.0.1/wss/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE", - false, - ), - ]; - - for (addr, should_be_valid) in variants { - let parsed: Multiaddr = addr.parse().unwrap(); - let result = craft_valid_multiaddr(&parsed); - - if should_be_valid { - let crafted = result.unwrap_or_else(|| panic!("Expected valid multiaddr: {addr}")); - assert_eq!(crafted.to_string(), parsed.to_string()); - } else { - assert!(result.is_none(), "Expected invalid multiaddr: {addr}"); - } - } + fn craft_valid_multiaddr_rejects_address_without_ip() { + let input = format!("/udp/8080/quic-v1/p2p/{VALID_PEER_ID}"); + let addr: Multiaddr = input.parse().unwrap(); + + let result = craft_valid_multiaddr(&addr); + + assert!(result.is_none(), "should reject address without ip"); } #[test] - fn test_craft_valid_multiaddr_from_str() { - let valid = "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE"; - assert!(craft_valid_multiaddr_from_str(valid).is_some()); + fn craft_valid_multiaddr_from_str_accepts_valid_address() { + let input = format!("/ip4/127.0.0.1/udp/8080/quic-v1/p2p/{VALID_PEER_ID}"); - let invalid = "not a multiaddr"; - assert!(craft_valid_multiaddr_from_str(invalid).is_none()); + let result = + craft_valid_multiaddr_from_str(&input).expect("should parse valid address string"); - let missing_peer = "/ip4/127.0.0.1/tcp/8080"; - assert!(craft_valid_multiaddr_from_str(missing_peer).is_none()); - assert!(craft_valid_multiaddr_from_str(missing_peer).is_some()); + assert_eq!(result.to_string(), input); } #[test] - fn test_craft_valid_multiaddr_ignore_peer_id() { - let addr_without_peer: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap(); - assert!(craft_valid_multiaddr(&addr_without_peer).is_none()); - assert!(craft_valid_multiaddr(&addr_without_peer).is_some()); + fn craft_valid_multiaddr_from_str_rejects_invalid_string() { + let result = craft_valid_multiaddr_from_str("not a multiaddr"); + + assert!(result.is_none(), "should reject invalid multiaddr string"); } #[test] - fn test_multiaddr_get_peer_id() { - let addr_with_peer: Multiaddr = - "/ip4/127.0.0.1/udp/8080/quic-v1/p2p/12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhgFRcw3UERE" - .parse() - .unwrap(); - assert!(multiaddr_get_peer_id(&addr_with_peer).is_some()); - - let addr_without_peer: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap(); - assert!(multiaddr_get_peer_id(&addr_without_peer).is_none()); + fn craft_valid_multiaddr_from_str_accepts_address_without_peer_id() { + let input = "/ip4/127.0.0.1/udp/8080/quic-v1"; + + let result = craft_valid_multiaddr_from_str(input) + .expect("should accept address string without peer id"); + + assert_eq!(result.to_string(), input); + } + + #[test] + fn craft_valid_multiaddr_from_str_rejects_tcp_address() { + let input = format!("/ip4/127.0.0.1/tcp/8080/p2p/{VALID_PEER_ID}"); + + let result = craft_valid_multiaddr_from_str(&input); + + assert!(result.is_none(), "should reject tcp address"); + } + + #[test] + fn multiaddr_get_peer_id_extracts_peer_id_when_present() { + let addr: Multiaddr = format!("/ip4/127.0.0.1/udp/8080/quic-v1/p2p/{VALID_PEER_ID}") + .parse() + .unwrap(); + + let peer_id = multiaddr_get_peer_id(&addr).expect("should extract peer id"); + + assert_eq!(peer_id.to_string(), VALID_PEER_ID); + } + + #[test] + fn multiaddr_get_peer_id_returns_none_when_absent() { + let addr: Multiaddr = "/ip4/127.0.0.1/udp/8080/quic-v1".parse().unwrap(); + + let result = multiaddr_get_peer_id(&addr); + + assert!( + result.is_none(), + "should return None when peer id is absent" + ); } } diff --git a/ant-node/src/bin/antnode/main.rs b/ant-node/src/bin/antnode/main.rs index 2359ba8be6..59b7540b24 100644 --- a/ant-node/src/bin/antnode/main.rs +++ b/ant-node/src/bin/antnode/main.rs @@ -300,7 +300,7 @@ fn main() -> Result<()> { let bootstrap_config = BootstrapConfig::try_from(&opt.peers)? .with_backwards_compatible_writes(opt.write_older_cache_files); - let bootstrap = Bootstrap::new(bootstrap_config)?; + let bootstrap = rt.block_on(async { Bootstrap::new(bootstrap_config) })?; let msg = format!( "Running {} v{}",