diff --git a/network/src/overlay/overlay_id.rs b/network/src/overlay/overlay_id.rs index f2da897d8..c527edcbf 100644 --- a/network/src/overlay/overlay_id.rs +++ b/network/src/overlay/overlay_id.rs @@ -6,10 +6,10 @@ use tl_proto::{TlRead, TlWrite}; #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, TlRead, TlWrite)] #[repr(transparent)] -pub struct OverlayId([u8; 32]); +pub struct OverlayId(pub [u8; 32]); impl OverlayId { - pub fn wrap(bytes: &[u8; 32]) -> &Self { + pub const fn wrap(bytes: &[u8; 32]) -> &Self { // SAFETY: `[u8; 32]` has the same layout as `OverlayId`. unsafe { &*(bytes as *const [u8; 32]).cast::() } } diff --git a/network/src/types/peer_id.rs b/network/src/types/peer_id.rs index cfad5d472..60cb49e09 100644 --- a/network/src/types/peer_id.rs +++ b/network/src/types/peer_id.rs @@ -10,7 +10,7 @@ use tl_proto::{TlRead, TlWrite}; pub struct PeerId(pub [u8; 32]); impl PeerId { - pub fn wrap(bytes: &[u8; 32]) -> &Self { + pub const fn wrap(bytes: &[u8; 32]) -> &Self { // SAFETY: `[u8; 32]` has the same layout as `PeerId`. unsafe { &*(bytes as *const [u8; 32]).cast::() } } diff --git a/network/src/types/request.rs b/network/src/types/request.rs index a641e5d7e..8c337b975 100644 --- a/network/src/types/request.rs +++ b/network/src/types/request.rs @@ -95,7 +95,7 @@ impl Response { } } - pub fn parse_tl(self) -> tl_proto::TlResult + pub fn parse_tl(&self) -> tl_proto::TlResult where for<'a> T: tl_proto::TlRead<'a, Repr = tl_proto::Boxed>, { @@ -115,6 +115,15 @@ pub struct ServiceRequest { pub body: Bytes, } +impl ServiceRequest { + pub fn parse_tl(&self) -> tl_proto::TlResult + where + for<'a> T: tl_proto::TlRead<'a, Repr = tl_proto::Boxed>, + { + tl_proto::deserialize(self.body.as_ref()) + } +} + impl AsRef<[u8]> for ServiceRequest { #[inline] fn as_ref(&self) -> &[u8] { diff --git a/network/tests/dht.rs b/network/tests/dht.rs index cb80aa3da..b8a190454 100644 --- a/network/tests/dht.rs +++ b/network/tests/dht.rs @@ -20,8 +20,8 @@ struct Node { } impl Node { - fn new(key: &ed25519::SecretKey) -> Result { - let keypair = everscale_crypto::ed25519::KeyPair::from(key); + fn new(key: &ed25519::SecretKey) -> Self { + let keypair = ed25519::KeyPair::from(key); let (dht_client, dht) = DhtService::builder(keypair.public_key.into()).build(); @@ -35,7 +35,7 @@ impl Node { let dht = dht_client.build(network.clone()); - Ok(Self { network, dht }) + Self { network, dht } } fn make_peer_info(key: &ed25519::SecretKey, address: Address) -> PeerInfo { @@ -151,7 +151,7 @@ async fn connect_new_node_to_bootstrap() -> Result<()> { let (bootstrap_nodes, global_config) = make_network(5); - let node = Node::new(&ed25519::SecretKey::generate(&mut rand::thread_rng()))?; + let node = Node::new(&ed25519::SecretKey::generate(&mut rand::thread_rng())); for peer_info in &global_config { node.dht.add_peer(peer_info.clone())?; } diff --git a/network/tests/overlay.rs b/network/tests/overlay.rs new file mode 100644 index 000000000..c376e35ec --- /dev/null +++ b/network/tests/overlay.rs @@ -0,0 +1,176 @@ +//! Run tests with this env: +//! ```text +//! RUST_LOG=info,tycho_network=trace +//! ``` + +use anyhow::Result; +use everscale_crypto::ed25519; +use std::net::Ipv4Addr; +use std::sync::Arc; +use tl_proto::{TlRead, TlWrite}; +use tycho_network::{ + Address, Network, OverlayId, OverlayService, PeerAffinity, PeerId, PeerInfo, PrivateOverlay, + Request, Response, Router, Service, ServiceRequest, +}; +use tycho_util::time::now_sec; + +struct Node { + network: Network, + private_overlay: PrivateOverlay, +} + +impl Node { + fn new(key: &ed25519::SecretKey) -> Self { + let keypair = ed25519::KeyPair::from(key); + let local_id = PeerId::from(keypair.public_key); + + let private_overlay = PrivateOverlay::builder(PRIVATE_OVERLAY_ID).build(PingPongService); + + let (overlay_tasks, overlay_service) = OverlayService::builder(local_id) + .with_private_overlay(&private_overlay) + .build(); + + let router = Router::builder().route(overlay_service).build(); + + let network = Network::builder() + .with_private_key(key.to_bytes()) + .with_service_name("test-service") + .build((Ipv4Addr::LOCALHOST, 0), router) + .unwrap(); + + overlay_tasks.spawn(network.clone()); + + Self { + network, + private_overlay, + } + } + + fn make_peer_info(key: &ed25519::SecretKey, address: Address) -> PeerInfo { + let keypair = ed25519::KeyPair::from(key); + let peer_id = PeerId::from(keypair.public_key); + + let now = now_sec(); + let mut node_info = PeerInfo { + id: peer_id, + address_list: vec![address].into_boxed_slice(), + created_at: now, + expires_at: u32::MAX, + signature: Box::new([0; 64]), + }; + *node_info.signature = keypair.sign(&node_info); + node_info + } + + async fn private_overlay_query(&self, peer_id: &PeerId, req: Q) -> Result + where + Q: tl_proto::TlWrite, + for<'a> A: tl_proto::TlRead<'a, Repr = tl_proto::Boxed>, + { + self.private_overlay + .query(&self.network, peer_id, Request::from_tl(req)) + .await? + .parse_tl::() + .map_err(Into::into) + } +} + +fn make_network(node_count: usize) -> Vec { + let keys = (0..node_count) + .map(|_| ed25519::SecretKey::generate(&mut rand::thread_rng())) + .collect::>(); + + let nodes = keys.iter().map(Node::new).collect::>(); + + let bootstrap_info = std::iter::zip(&keys, &nodes) + .map(|(key, node)| Arc::new(Node::make_peer_info(key, node.network.local_addr().into()))) + .collect::>(); + + for node in &nodes { + let mut private_overlay_entries = node.private_overlay.write_entries(); + + for info in &bootstrap_info { + node.network + .known_peers() + .insert(info.clone(), PeerAffinity::Allowed); + + private_overlay_entries.insert(&info.id); + } + } + + nodes +} + +#[tokio::test] +async fn private_overlays_accessible() -> Result<()> { + tracing_subscriber::fmt::try_init().ok(); + tracing::info!("bootstrap_nodes_accessible"); + + let nodes = make_network(5); + + for i in 0..nodes.len() { + for j in 0..nodes.len() { + if i == j { + continue; + } + + let left = &nodes[i]; + let right = &nodes[j]; + + let value = (i * 1000 + j) as u64; + let Pong { value: received } = left + .private_overlay_query(right.network.peer_id(), Ping { value }) + .await?; + assert_eq!(received, value); + } + } + + Ok(()) +} + +struct PingPongService; + +impl Service for PingPongService { + type QueryResponse = Response; + type OnQueryFuture = futures_util::future::Ready>; + type OnMessageFuture = futures_util::future::Ready<()>; + type OnDatagramFuture = futures_util::future::Ready<()>; + + fn on_query(&self, req: ServiceRequest) -> Self::OnQueryFuture { + futures_util::future::ready(match req.parse_tl() { + Ok(Ping { value }) => Some(Response::from_tl(Pong { value })), + Err(e) => { + tracing::error!( + peer_id = %req.metadata.peer_id, + addr = %req.metadata.remote_address, + "invalid request: {e:?}", + ); + None + } + }) + } + + #[inline] + fn on_message(&self, _req: ServiceRequest) -> Self::OnMessageFuture { + futures_util::future::ready(()) + } + + #[inline] + fn on_datagram(&self, _req: ServiceRequest) -> Self::OnDatagramFuture { + futures_util::future::ready(()) + } +} + +#[derive(Debug, Copy, Clone, TlRead, TlWrite)] +#[tl(boxed, id = 0x11223344)] +struct Ping { + value: u64, +} + +#[derive(Debug, Copy, Clone, TlRead, TlWrite)] +#[tl(boxed, id = 0x55667788)] +struct Pong { + value: u64, +} + +static PRIVATE_OVERLAY_ID: OverlayId = OverlayId([0; 32]);