diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index e2e712bfbc7..e048129b914 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -11,6 +11,9 @@ use zebra_chain::{parameters::Network, serialization::canonical_socket_addr}; use crate::{constants, BoxError}; +#[cfg(test)] +mod tests; + /// The number of times Zebra will retry each initial peer's DNS resolution, /// before checking if any other initial peers have returned addresses. const MAX_SINGLE_PEER_RETRIES: usize = 2; @@ -291,6 +294,7 @@ impl<'de> Deserialize<'de> for Config { } let config = DConfig::deserialize(deserializer)?; + // TODO: perform listener DNS lookups asynchronously with a timeout (#1631) let listen_addr = match config.listen_addr.parse::() { Ok(socket) => Ok(socket), @@ -313,6 +317,3 @@ impl<'de> Deserialize<'de> for Config { }) } } - -#[cfg(test)] -mod tests; diff --git a/zebra-network/src/peer_set/initialize.rs b/zebra-network/src/peer_set/initialize.rs index 4b6b20b6edd..ad22e6519f5 100644 --- a/zebra-network/src/peer_set/initialize.rs +++ b/zebra-network/src/peer_set/initialize.rs @@ -63,6 +63,11 @@ type PeerChange = Result, BoxError>; /// In addition to returning a service for outbound requests, this method /// returns a shared [`AddressBook`] updated with last-seen timestamps for /// connected peers. +/// +/// # Panics +/// +/// If `config.config.peerset_initial_target_size` is zero. +/// (zebra-network expects to be able to connect to at least one peer.) pub async fn init( config: Config, inbound_service: S, @@ -76,6 +81,13 @@ where S::Future: Send + 'static, C: ChainTip + Clone + Send + 'static, { + // If we want Zebra to operate with no network, + // we should implement a `zebrad` command that doesn't use `zebra-network`. + assert!( + config.peerset_initial_target_size > 0, + "Zebra must be allowed to connect to at least one peer" + ); + let (tcp_listener, listen_addr) = open_listener(&config.clone()).await; let (address_book, timestamp_collector) = TimestampCollector::spawn(listen_addr); diff --git a/zebra-network/src/peer_set/initialize/tests/vectors.rs b/zebra-network/src/peer_set/initialize/tests/vectors.rs index 310d07f26f9..20bc8034f9c 100644 --- a/zebra-network/src/peer_set/initialize/tests/vectors.rs +++ b/zebra-network/src/peer_set/initialize/tests/vectors.rs @@ -1,4 +1,4 @@ -//! Specific configs used for zebra-network initialization tests. +//! zebra-network initialization tests using fixed configs. //! //! ## Failures due to Port Conflicts //! @@ -13,16 +13,14 @@ //! If it does not have any IPv4 interfaces, or IPv4 localhost is not on `127.0.0.1`, //! skip all the network tests by setting the `ZEBRA_SKIP_NETWORK_TESTS` environmental variable. -use std::{collections::HashSet, net::SocketAddr}; +use std::{collections::HashSet, net::SocketAddr, sync::Arc}; -use tower::service_fn; +use tower::{service_fn, Service}; use zebra_chain::{chain_tip::NoChainTip, parameters::Network}; use zebra_test::net::random_known_port; -use crate::Config; - -use super::super::init; +use crate::{init, AddressBook, BoxError, Config, Request, Response}; use Network::*; @@ -98,6 +96,114 @@ async fn local_listener_fixed_port_localhost_addr() { local_listener_port_with(SocketAddr::new(localhost_v6, random_known_port()), Testnet).await; } +/// Test zebra-network with a peer limit of zero peers on mainnet. +/// (Zebra does not support this mode of operation.) +#[tokio::test] +#[should_panic] +async fn peer_limit_zero_mainnet() { + zebra_test::init(); + + // This test should not require network access, because the connection limit is zero. + + let unreachable_inbound_service = + service_fn(|_| async { unreachable!("inbound service should never be called") }); + + let address_book = init_with(0, unreachable_inbound_service, Mainnet).await; + assert_eq!( + address_book.lock().unwrap().peers().count(), + 0, + "expected no peers in Mainnet address book, but got: {:?}", + address_book.lock().unwrap().address_metrics() + ); +} + +/// Test zebra-network with a peer limit of zero peers on testnet. +/// (Zebra does not support this mode of operation.) +#[tokio::test] +#[should_panic] +async fn peer_limit_zero_testnet() { + zebra_test::init(); + + // This test should not require network access, because the connection limit is zero. + + let unreachable_inbound_service = + service_fn(|_| async { unreachable!("inbound service should never be called") }); + + let address_book = init_with(0, unreachable_inbound_service, Testnet).await; + assert_eq!( + address_book.lock().unwrap().peers().count(), + 0, + "expected no peers in Testnet address book, but got: {:?}", + address_book.lock().unwrap().address_metrics() + ); +} + +/// Test zebra-network with a peer limit of one inbound and one outbound peer on mainnet. +#[tokio::test] +async fn peer_limit_one_mainnet() { + zebra_test::init(); + + if zebra_test::net::zebra_skip_network_tests() { + return; + } + + let nil_inbound_service = service_fn(|_| async { Ok(Response::Nil) }); + + let _ = init_with(1, nil_inbound_service, Mainnet).await; + + // Any number of address book peers is valid here, because some peers might have failed. +} + +/// Test zebra-network with a peer limit of one inbound and one outbound peer on testnet. +#[tokio::test] +async fn peer_limit_one_testnet() { + zebra_test::init(); + + if zebra_test::net::zebra_skip_network_tests() { + return; + } + + let nil_inbound_service = service_fn(|_| async { Ok(Response::Nil) }); + + let _ = init_with(1, nil_inbound_service, Testnet).await; + + // Any number of address book peers is valid here, because some peers might have failed. +} + +/// Test zebra-network with a peer limit of two inbound and three outbound peers on mainnet. +#[tokio::test] +async fn peer_limit_two_mainnet() { + zebra_test::init(); + + if zebra_test::net::zebra_skip_network_tests() { + return; + } + + let nil_inbound_service = service_fn(|_| async { Ok(Response::Nil) }); + + let _ = init_with(2, nil_inbound_service, Mainnet).await; + + // Any number of address book peers is valid here, because some peers might have failed. +} + +/// Test zebra-network with a peer limit of two inbound and three outbound peers on testnet. +#[tokio::test] +async fn peer_limit_two_testnet() { + zebra_test::init(); + + if zebra_test::net::zebra_skip_network_tests() { + return; + } + + let nil_inbound_service = service_fn(|_| async { Ok(Response::Nil) }); + + let _ = init_with(2, nil_inbound_service, Testnet).await; + + // Any number of address book peers is valid here, because some peers might have failed. +} + +/// Open a local listener on `listen_addr` for `network`. +/// Asserts that the local listener address works as expected. async fn local_listener_port_with(listen_addr: SocketAddr, network: Network) { let config = Config { listen_addr, @@ -133,3 +239,32 @@ async fn local_listener_port_with(listen_addr: SocketAddr, network: Network) { "IP addresses are correctly propagated" ); } + +/// Initialize a peer set with `peerset_initial_target_size` and `inbound_service` on `network`. +/// Returns the newly created [`AddressBook`] for testing. +async fn init_with( + peerset_initial_target_size: usize, + inbound_service: S, + network: Network, +) -> Arc> +where + S: Service + Clone + Send + 'static, + S::Future: Send + 'static, +{ + // This test might fail on machines with no configured IPv4 addresses + // (localhost should be enough). + let unused_v4 = "0.0.0.0:0".parse().unwrap(); + + let config = Config { + peerset_initial_target_size, + + network, + listen_addr: unused_v4, + + ..Config::default() + }; + + let (_peer_service, address_book) = init(config, inbound_service, NoChainTip).await; + + address_book +}