546 lines
18 KiB
Rust
546 lines
18 KiB
Rust
//! Fixed test vectors for the peer set.
|
|
|
|
use std::{iter, time::Duration};
|
|
|
|
use tokio::time::timeout;
|
|
use tower::{Service, ServiceExt};
|
|
|
|
use zebra_chain::{
|
|
block,
|
|
parameters::{Network, NetworkUpgrade},
|
|
};
|
|
|
|
use super::{PeerSetBuilder, PeerVersions};
|
|
use crate::{
|
|
peer::{ClientRequest, MinimumPeerVersion},
|
|
peer_set::inventory_registry::InventoryStatus,
|
|
protocol::external::{types::Version, InventoryHash},
|
|
Request, SharedPeerError,
|
|
};
|
|
|
|
#[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())
|
|
.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());
|
|
});
|
|
}
|
|
|
|
/// 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())
|
|
.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())
|
|
.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())
|
|
.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::<SharedPeerError>()
|
|
.expect("peer set should return a boxed SharedPeerError")
|
|
.inner_debug(),
|
|
"NotFoundRegistry([Block(block::Hash(\"0000000000000000000000000000000000000000000000000000000000000000\"))])"
|
|
);
|
|
});
|
|
}
|