//! Fixed test vectors for the peer set. use std::{cmp::max, iter, time::Duration}; use tokio::time::timeout; use tower::{Service, ServiceExt}; use zebra_chain::{ block, parameters::{Network, NetworkUpgrade}, }; use crate::{ constants::DEFAULT_MAX_CONNS_PER_IP, peer::{ClientRequest, MinimumPeerVersion}, peer_set::inventory_registry::InventoryStatus, protocol::external::{types::Version, InventoryHash}, Request, SharedPeerError, }; use super::{PeerSetBuilder, PeerVersions}; #[test] fn peer_set_ready_single_connection() { // We are going to use just one peer version in this test let peer_versions = PeerVersions { peer_versions: vec![Version::min_specified_for_upgrade( &Network::Mainnet, NetworkUpgrade::Nu5, )], }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Get peers and client handles of them let (discovered_peers, handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // We will just use the first peer handle let mut client_handle = handles .into_iter() .next() .expect("we always have at least one client"); // Client did not received anything yet assert!(client_handle .try_to_receive_outbound_client_request() .is_empty()); runtime.block_on(async move { // Build a peerset let (mut peer_set, _peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .build(); // Get a ready future let peer_ready_future = peer_set.ready(); // If the readiness future gains a `Drop` impl, we want it to be called here. #[allow(unknown_lints)] #[allow(clippy::drop_non_drop)] std::mem::drop(peer_ready_future); // Peer set will remain ready for requests let peer_ready1 = peer_set .ready() .await .expect("peer set service is always ready"); // Make sure the client did not received anything yet assert!(client_handle .try_to_receive_outbound_client_request() .is_empty()); // Make a call to the peer set that returns a future let fut = peer_ready1.call(Request::Peers); // Client received the request assert!(matches!( client_handle .try_to_receive_outbound_client_request() .request(), Some(ClientRequest { request: Request::Peers, .. }) )); // Drop the future std::mem::drop(fut); // Peer set will remain ready for requests let peer_ready2 = peer_set .ready() .await .expect("peer set service is always ready"); // Get a new future calling a different request than before let _fut = peer_ready2.call(Request::MempoolTransactionIds); // Client received the request assert!(matches!( client_handle .try_to_receive_outbound_client_request() .request(), Some(ClientRequest { request: Request::MempoolTransactionIds, .. }) )); }); } #[test] fn peer_set_ready_multiple_connections() { // Use three peers with the same version let peer_version = Version::min_specified_for_upgrade(&Network::Mainnet, NetworkUpgrade::Nu5); let peer_versions = PeerVersions { peer_versions: vec![peer_version, peer_version, peer_version], }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Pause the runtime's timer so that it advances automatically. // // CORRECTNESS: This test does not depend on external resources that could really timeout, like // real network connections. tokio::time::pause(); // Get peers and client handles of them let (discovered_peers, handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // Make sure we have the right number of peers assert_eq!(handles.len(), 3); runtime.block_on(async move { // Build a peerset let (mut peer_set, _peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .max_conns_per_ip(max(3, DEFAULT_MAX_CONNS_PER_IP)) .build(); // Get peerset ready let peer_ready = peer_set .ready() .await .expect("peer set service is always ready"); // Check we have the right amount of ready services assert_eq!(peer_ready.ready_services.len(), 3); // Stop some peer connections but not all handles[0].stop_connection_task().await; handles[1].stop_connection_task().await; // We can still make the peer set ready peer_set .ready() .await .expect("peer set service is always ready"); // Stop the connection of the last peer handles[2].stop_connection_task().await; // Peer set hangs when no more connections are present let peer_ready = peer_set.ready(); assert!(timeout(Duration::from_secs(10), peer_ready).await.is_err()); }); } #[test] fn peer_set_rejects_connections_past_per_ip_limit() { const NUM_PEER_VERSIONS: usize = crate::constants::DEFAULT_MAX_CONNS_PER_IP + 1; // Use three peers with the same version let peer_version = Version::min_specified_for_upgrade(&Network::Mainnet, NetworkUpgrade::Nu5); let peer_versions = PeerVersions { peer_versions: [peer_version; NUM_PEER_VERSIONS].into_iter().collect(), }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Pause the runtime's timer so that it advances automatically. // // CORRECTNESS: This test does not depend on external resources that could really timeout, like // real network connections. tokio::time::pause(); // Get peers and client handles of them let (discovered_peers, handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // Make sure we have the right number of peers assert_eq!(handles.len(), NUM_PEER_VERSIONS); runtime.block_on(async move { // Build a peerset let (mut peer_set, _peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .build(); // Get peerset ready let peer_ready = peer_set .ready() .await .expect("peer set service is always ready"); // Check we have the right amount of ready services assert_eq!( peer_ready.ready_services.len(), crate::constants::DEFAULT_MAX_CONNS_PER_IP ); }); } /// Check that a peer set with an empty inventory registry routes requests to a random ready peer. #[test] fn peer_set_route_inv_empty_registry() { let test_hash = block::Hash([0; 32]); // Use two peers with the same version let peer_version = Version::min_specified_for_upgrade(&Network::Mainnet, NetworkUpgrade::Nu5); let peer_versions = PeerVersions { peer_versions: vec![peer_version, peer_version], }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Pause the runtime's timer so that it advances automatically. // // CORRECTNESS: This test does not depend on external resources that could really timeout, like // real network connections. tokio::time::pause(); // Get peers and client handles of them let (discovered_peers, handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // Make sure we have the right number of peers assert_eq!(handles.len(), 2); runtime.block_on(async move { // Build a peerset let (mut peer_set, _peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .max_conns_per_ip(max(2, DEFAULT_MAX_CONNS_PER_IP)) .build(); // Get peerset ready let peer_ready = peer_set .ready() .await .expect("peer set service is always ready"); // Check we have the right amount of ready services assert_eq!(peer_ready.ready_services.len(), 2); // Send an inventory-based request let sent_request = Request::BlocksByHash(iter::once(test_hash).collect()); let _fut = peer_ready.call(sent_request.clone()); // Check that one of the clients received the request let mut received_count = 0; for mut handle in handles { if let Some(ClientRequest { request, .. }) = handle.try_to_receive_outbound_client_request().request() { assert_eq!(sent_request, request); received_count += 1; } } assert_eq!(received_count, 1); }); } /// Check that a peer set routes inventory requests to a peer that has advertised that inventory. #[test] fn peer_set_route_inv_advertised_registry() { peer_set_route_inv_advertised_registry_order(true); peer_set_route_inv_advertised_registry_order(false); } fn peer_set_route_inv_advertised_registry_order(advertised_first: bool) { let test_hash = block::Hash([0; 32]); let test_inv = InventoryHash::Block(test_hash); // Hard-code the fixed test address created by mock_peer_discovery // TODO: add peer test addresses to ClientTestHarness let test_peer = if advertised_first { "127.0.0.1:1" } else { "127.0.0.1:2" } .parse() .expect("unexpected invalid peer address"); let test_change = InventoryStatus::new_available(test_inv, test_peer); // Use two peers with the same version let peer_version = Version::min_specified_for_upgrade(&Network::Mainnet, NetworkUpgrade::Nu5); let peer_versions = PeerVersions { peer_versions: vec![peer_version, peer_version], }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Pause the runtime's timer so that it advances automatically. // // CORRECTNESS: This test does not depend on external resources that could really timeout, like // real network connections. tokio::time::pause(); // Get peers and client handles of them let (discovered_peers, mut handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // Make sure we have the right number of peers assert_eq!(handles.len(), 2); runtime.block_on(async move { // Build a peerset let (mut peer_set, mut peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .max_conns_per_ip(max(2, DEFAULT_MAX_CONNS_PER_IP)) .build(); // Advertise some inventory peer_set_guard .inventory_sender() .as_mut() .expect("unexpected missing inv sender") .send(test_change) .expect("unexpected dropped receiver"); // Get peerset ready let peer_ready = peer_set .ready() .await .expect("peer set service is always ready"); // Check we have the right amount of ready services assert_eq!(peer_ready.ready_services.len(), 2); // Send an inventory-based request let sent_request = Request::BlocksByHash(iter::once(test_hash).collect()); let _fut = peer_ready.call(sent_request.clone()); // Check that the client that advertised the inventory received the request let advertised_handle = if advertised_first { &mut handles[0] } else { &mut handles[1] }; if let Some(ClientRequest { request, .. }) = advertised_handle .try_to_receive_outbound_client_request() .request() { assert_eq!(sent_request, request); } else { panic!("inv request not routed to advertised peer"); } let other_handle = if advertised_first { &mut handles[1] } else { &mut handles[0] }; assert!( other_handle .try_to_receive_outbound_client_request() .request() .is_none(), "request routed to non-advertised peer", ); }); } /// Check that a peer set routes inventory requests to peers that are not missing that inventory. #[test] fn peer_set_route_inv_missing_registry() { peer_set_route_inv_missing_registry_order(true); peer_set_route_inv_missing_registry_order(false); } fn peer_set_route_inv_missing_registry_order(missing_first: bool) { let test_hash = block::Hash([0; 32]); let test_inv = InventoryHash::Block(test_hash); // Hard-code the fixed test address created by mock_peer_discovery // TODO: add peer test addresses to ClientTestHarness let test_peer = if missing_first { "127.0.0.1:1" } else { "127.0.0.1:2" } .parse() .expect("unexpected invalid peer address"); let test_change = InventoryStatus::new_missing(test_inv, test_peer); // Use two peers with the same version let peer_version = Version::min_specified_for_upgrade(&Network::Mainnet, NetworkUpgrade::Nu5); let peer_versions = PeerVersions { peer_versions: vec![peer_version, peer_version], }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Pause the runtime's timer so that it advances automatically. // // CORRECTNESS: This test does not depend on external resources that could really timeout, like // real network connections. tokio::time::pause(); // Get peers and client handles of them let (discovered_peers, mut handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // Make sure we have the right number of peers assert_eq!(handles.len(), 2); runtime.block_on(async move { // Build a peerset let (mut peer_set, mut peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .max_conns_per_ip(max(2, DEFAULT_MAX_CONNS_PER_IP)) .build(); // Mark some inventory as missing peer_set_guard .inventory_sender() .as_mut() .expect("unexpected missing inv sender") .send(test_change) .expect("unexpected dropped receiver"); // Get peerset ready let peer_ready = peer_set .ready() .await .expect("peer set service is always ready"); // Check we have the right amount of ready services assert_eq!(peer_ready.ready_services.len(), 2); // Send an inventory-based request let sent_request = Request::BlocksByHash(iter::once(test_hash).collect()); let _fut = peer_ready.call(sent_request.clone()); // Check that the client missing the inventory did not receive the request let missing_handle = if missing_first { &mut handles[0] } else { &mut handles[1] }; assert!( missing_handle .try_to_receive_outbound_client_request() .request() .is_none(), "request routed to missing peer", ); // Check that the client that was not missing the inventory received the request let other_handle = if missing_first { &mut handles[1] } else { &mut handles[0] }; if let Some(ClientRequest { request, .. }) = other_handle .try_to_receive_outbound_client_request() .request() { assert_eq!(sent_request, request); } else { panic!( "inv request should have been routed to the only peer not missing the inventory" ); } }); } /// Check that a peer set fails inventory requests if all peers are missing that inventory. #[test] fn peer_set_route_inv_all_missing_fail() { let test_hash = block::Hash([0; 32]); let test_inv = InventoryHash::Block(test_hash); // Hard-code the fixed test address created by mock_peer_discovery // TODO: add peer test addresses to ClientTestHarness let test_peer = "127.0.0.1:1" .parse() .expect("unexpected invalid peer address"); let test_change = InventoryStatus::new_missing(test_inv, test_peer); // Use one peer let peer_version = Version::min_specified_for_upgrade(&Network::Mainnet, NetworkUpgrade::Nu5); let peer_versions = PeerVersions { peer_versions: vec![peer_version], }; // Start the runtime let (runtime, _init_guard) = zebra_test::init_async(); let _guard = runtime.enter(); // Pause the runtime's timer so that it advances automatically. // // CORRECTNESS: This test does not depend on external resources that could really timeout, like // real network connections. tokio::time::pause(); // Get the peer and its client handle let (discovered_peers, mut handles) = peer_versions.mock_peer_discovery(); let (minimum_peer_version, _best_tip_height) = MinimumPeerVersion::with_mock_chain_tip(&Network::Mainnet); // Make sure we have the right number of peers assert_eq!(handles.len(), 1); runtime.block_on(async move { // Build a peerset let (mut peer_set, mut peer_set_guard) = PeerSetBuilder::new() .with_discover(discovered_peers) .with_minimum_peer_version(minimum_peer_version.clone()) .build(); // Mark the inventory as missing for all peers peer_set_guard .inventory_sender() .as_mut() .expect("unexpected missing inv sender") .send(test_change) .expect("unexpected dropped receiver"); // Get peerset ready let peer_ready = peer_set .ready() .await .expect("peer set service is always ready"); // Check we have the right amount of ready services assert_eq!(peer_ready.ready_services.len(), 1); // Send an inventory-based request let sent_request = Request::BlocksByHash(iter::once(test_hash).collect()); let response_fut = peer_ready.call(sent_request.clone()); // Check that the client missing the inventory did not receive the request let missing_handle = &mut handles[0]; assert!( missing_handle .try_to_receive_outbound_client_request() .request().is_none(), "request routed to missing peer", ); // Check that the response is a synthetic error let response = response_fut.await; assert_eq!( response .expect_err("peer set should return an error (not a Response)") .downcast_ref::() .expect("peer set should return a boxed SharedPeerError") .inner_debug(), "NotFoundRegistry([Block(block::Hash(\"0000000000000000000000000000000000000000000000000000000000000000\"))])" ); }); }