From cbbbb22e3aea3f947c6010d4e8feef1b35f1d8ca Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Fri, 16 Nov 2018 12:14:35 +0100 Subject: [PATCH 01/11] Rework Kademlia for the new design --- core/src/nodes/swarm.rs | 20 +- core/src/peer_id.rs | 7 + core/src/topology/mod.rs | 7 + examples/ipfs-kad.rs | 98 +++ misc/core-derive/src/lib.rs | 29 +- misc/core-derive/tests/test.rs | 5 +- protocols/floodsub/src/layer.rs | 3 +- protocols/identify/src/listen_layer.rs | 3 +- protocols/identify/src/periodic_id_layer.rs | 3 +- protocols/kad/Cargo.toml | 1 + protocols/kad/src/behaviour.rs | 593 +++++++++++++++ protocols/kad/src/handler.rs | 772 ++++++++++++++++++++ protocols/kad/src/high_level.rs | 466 ------------ protocols/kad/src/kad_server.rs | 620 ---------------- protocols/kad/src/kbucket.rs | 33 +- protocols/kad/src/lib.rs | 23 +- protocols/kad/src/protocol.rs | 425 ++++++----- protocols/kad/src/query.rs | 715 ++++++++++-------- protocols/kad/src/topology.rs | 73 ++ protocols/ping/src/dial_layer.rs | 3 +- protocols/ping/src/listen_layer.rs | 3 +- 21 files changed, 2288 insertions(+), 1614 deletions(-) create mode 100644 examples/ipfs-kad.rs create mode 100644 protocols/kad/src/behaviour.rs create mode 100644 protocols/kad/src/handler.rs delete mode 100644 protocols/kad/src/high_level.rs delete mode 100644 protocols/kad/src/kad_server.rs create mode 100644 protocols/kad/src/topology.rs diff --git a/core/src/nodes/swarm.rs b/core/src/nodes/swarm.rs index 4320d1ad6c8..17e6c06be3f 100644 --- a/core/src/nodes/swarm.rs +++ b/core/src/nodes/swarm.rs @@ -35,12 +35,12 @@ use std::{fmt, io, ops::{Deref, DerefMut}}; /// Contains the state of the network, plus the way it should behave. pub struct Swarm where TTransport: Transport, - TBehaviour: NetworkBehaviour, + TBehaviour: NetworkBehaviour, { raw_swarm: RawSwarm< TTransport, - <::ProtocolsHandler as ProtocolsHandler>::InEvent, - <::ProtocolsHandler as ProtocolsHandler>::OutEvent, + <>::ProtocolsHandler as ProtocolsHandler>::InEvent, + <>::ProtocolsHandler as ProtocolsHandler>::OutEvent, NodeHandlerWrapper, >, @@ -55,7 +55,7 @@ where TTransport: Transport, impl Deref for Swarm where TTransport: Transport, - TBehaviour: NetworkBehaviour, + TBehaviour: NetworkBehaviour, { type Target = TBehaviour; @@ -67,7 +67,7 @@ where TTransport: Transport, impl DerefMut for Swarm where TTransport: Transport, - TBehaviour: NetworkBehaviour, + TBehaviour: NetworkBehaviour, { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { @@ -76,7 +76,7 @@ where TTransport: Transport, } impl Swarm -where TBehaviour: NetworkBehaviour, +where TBehaviour: NetworkBehaviour, TMuxer: StreamMuxer + Send + Sync + 'static, ::OutboundSubstream: Send + 'static, ::Substream: Send + 'static, @@ -169,7 +169,7 @@ where TBehaviour: NetworkBehaviour, } impl Stream for Swarm -where TBehaviour: NetworkBehaviour, +where TBehaviour: NetworkBehaviour, TMuxer: StreamMuxer + Send + Sync + 'static, ::OutboundSubstream: Send + 'static, ::Substream: Send + 'static, @@ -228,7 +228,7 @@ where TBehaviour: NetworkBehaviour, Async::Ready(RawSwarmEvent::UnknownPeerDialError { .. }) => {}, } - match self.behaviour.poll() { + match self.behaviour.poll(&mut self.topology) { Async::NotReady if raw_swarm_not_ready => return Ok(Async::NotReady), Async::NotReady => (), Async::Ready(NetworkBehaviourAction::GenerateEvent(event)) => { @@ -254,7 +254,7 @@ where TBehaviour: NetworkBehaviour, /// /// This trait has been designed to be composable. Multiple implementations can be combined into /// one that handles all the behaviours at once. -pub trait NetworkBehaviour { +pub trait NetworkBehaviour { /// Handler for all the protocols the network supports. type ProtocolsHandler: ProtocolsHandler; /// Event generated by the swarm. @@ -284,7 +284,7 @@ pub trait NetworkBehaviour { /// Polls for things that swarm should do. /// /// This API mimics the API of the `Stream` trait. - fn poll(&mut self) -> Async::InEvent, Self::OutEvent>>; + fn poll(&mut self, topology: &mut TTopology) -> Async::InEvent, Self::OutEvent>>; } /// Action to perform. diff --git a/core/src/peer_id.rs b/core/src/peer_id.rs index 2994cf62e7c..44d549fa8e2 100644 --- a/core/src/peer_id.rs +++ b/core/src/peer_id.rs @@ -147,6 +147,13 @@ impl PartialEq for multihash::Multihash { } } +impl AsRef for PeerId { + #[inline] + fn as_ref(&self) -> &multihash::Multihash { + &self.multihash + } +} + impl Into for PeerId { #[inline] fn into(self) -> multihash::Multihash { diff --git a/core/src/topology/mod.rs b/core/src/topology/mod.rs index f27a6512d37..04e7a22a1ec 100644 --- a/core/src/topology/mod.rs +++ b/core/src/topology/mod.rs @@ -27,6 +27,9 @@ pub trait Topology { fn add_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr); /// Returns the addresses to try use to reach the given peer. fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec; + /// Returns all the known peers. + // TODO: bad API ; also, is the method even a good idea? + fn peers(&self) -> Vec; } /// Topology of the network stored in memory. @@ -59,4 +62,8 @@ impl Topology for MemoryTopology { fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { self.list.get(peer).map(|v| v.clone()).unwrap_or(Vec::new()) } + + fn peers(&self) -> Vec { + self.list.keys().map(|k| k.clone()).collect() + } } diff --git a/examples/ipfs-kad.rs b/examples/ipfs-kad.rs new file mode 100644 index 00000000000..dffa055a9dc --- /dev/null +++ b/examples/ipfs-kad.rs @@ -0,0 +1,98 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Demonstrates how to perform Kademlia queries on the IPFS network. +//! +//! You can pass as parameter a base58 peer ID to search for. If you don't pass any parameter, a +//! peer ID will be generated randomly. + +extern crate futures; +extern crate libp2p; +extern crate rand; +extern crate tokio; + +use futures::prelude::*; +use libp2p::{ + Transport, + core::PublicKey, + core::upgrade::{self, OutboundUpgradeExt}, + core::topology::Topology, + secio, + mplex, +}; + +fn main() { + // Create a random key for ourselves. + let local_key = secio::SecioKeyPair::ed25519_generated().unwrap(); + let local_peer_id = local_key.to_peer_id(); + + // Set up a an encrypted DNS-enabled TCP Transport over the Mplex protocol + let transport = libp2p::CommonTransport::new() + .with_upgrade(secio::SecioConfig::new(local_key)) + .and_then(move |out, _| { + let peer_id = out.remote_key.into_peer_id(); + let upgrade = mplex::MplexConfig::new().map_outbound(move |muxer| (peer_id, muxer) ); + upgrade::apply_outbound(out.stream, upgrade).map_err(|e| e.into_io_error()) + }); + + // Create the topology of the network with the IPFS bootstrap nodes. + let mut topology = libp2p::core::topology::MemoryTopology::empty(); + topology.add_discovered_address(&"QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ".parse().unwrap(), "/ip4/104.131.131.82/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM".parse().unwrap(), "/ip4/104.236.179.241/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64".parse().unwrap(), "/ip4/104.236.76.40/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu".parse().unwrap(), "/ip4/128.199.219.111/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd".parse().unwrap(), "/ip4/178.62.158.247/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu".parse().unwrap(), "/ip6/2400:6180:0:d0::151:6001/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM".parse().unwrap(), "/ip6/2604:a880:1:20::203:d001/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64".parse().unwrap(), "/ip6/2604:a880:800:10::4a:5001/tcp/4001".parse().unwrap()); + topology.add_discovered_address(&"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd".parse().unwrap(), "/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001".parse().unwrap()); + + // Create a swarm to manage peers and events. + let mut swarm = { + // Create a Kademlia behaviour ; note that we use `without_init` because this example is + // very ephemeral. In a real world application, you want to use `new` instead. + let mut behaviour = libp2p::kad::Kademlia::without_init(local_peer_id); + libp2p::core::nodes::Swarm::new(transport, behaviour, topology) + }; + + // Order Kademlia to search for a peer. + let to_search = if let Some(peer_id) = std::env::args().nth(1) { + peer_id.parse().expect("Failed to parse peer ID to find") + } else { + PublicKey::Secp256k1((0..32).map(|_| -> u8 { rand::random() }).collect()).into_peer_id() + }; + println!("Searching for {:?}", to_search); + swarm.find_node(to_search); + + // Kick it off! + tokio::run(futures::future::poll_fn(move || -> Result<_, ()> { + loop { + match swarm.poll().expect("Error while polling swarm") { + Async::Ready(Some(event)) => { + println!("Result: {:#?}", event); + return Ok(Async::Ready(())); + }, + Async::Ready(None) | Async::NotReady => break, + } + } + + Ok(Async::NotReady) + })); +} diff --git a/misc/core-derive/src/lib.rs b/misc/core-derive/src/lib.rs index 167b4c46518..54c8b22ec97 100644 --- a/misc/core-derive/src/lib.rs +++ b/misc/core-derive/src/lib.rs @@ -68,6 +68,17 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { quote!{#n} }; + // Name of the type parameter that represents the topology. + let topology_generic = { + let mut n = "TTopology".to_string(); + // Avoid collisions. + while ast.generics.type_params().any(|tp| tp.ident.to_string() == n) { + n.push('1'); + } + let n = Ident::new(&n, name.span()); + quote!{#n} + }; + let output_types = { let mut start = 1; // Avoid collisions. @@ -87,7 +98,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { let lf = ast.generics.lifetimes(); let cst = ast.generics.const_params(); let out = output_types.clone(); - quote!{<#(#lf,)* #(#tp,)* #(#cst,)* #substream_generic, #(#out),*>} + quote!{<#(#lf,)* #(#tp,)* #(#cst,)* #topology_generic, #substream_generic, #(#out),*>} }; // Build the `where ...` clause of the trait implementation. @@ -98,11 +109,11 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { .flat_map(|(field, out)| { let ty = &field.ty; vec![ - quote!{#ty: #trait_to_impl}, - quote!{<#ty as #trait_to_impl>::ProtocolsHandler: #protocols_handler}, + quote!{#ty: #trait_to_impl<#topology_generic>}, + quote!{<#ty as #trait_to_impl<#topology_generic>>::ProtocolsHandler: #protocols_handler}, // Note: this bound is required because of https://github.com/rust-lang/rust/issues/55697 - quote!{<<#ty as #trait_to_impl>::ProtocolsHandler as #protocols_handler>::InboundProtocol: ::libp2p::core::InboundUpgrade<#substream_generic, Output = #out>}, - quote!{<<#ty as #trait_to_impl>::ProtocolsHandler as #protocols_handler>::OutboundProtocol: ::libp2p::core::OutboundUpgrade<#substream_generic, Output = #out>}, + quote!{<<#ty as #trait_to_impl<#topology_generic>>::ProtocolsHandler as #protocols_handler>::InboundProtocol: ::libp2p::core::InboundUpgrade<#substream_generic, Output = #out>}, + quote!{<<#ty as #trait_to_impl<#topology_generic>>::ProtocolsHandler as #protocols_handler>::OutboundProtocol: ::libp2p::core::OutboundUpgrade<#substream_generic, Output = #out>}, ] }) .collect::>(); @@ -211,7 +222,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { continue; } let ty = &field.ty; - let field_info = quote!{ <#ty as #trait_to_impl>::ProtocolsHandler }; + let field_info = quote!{ <#ty as #trait_to_impl<#topology_generic>>::ProtocolsHandler }; match ph_ty { Some(ev) => ph_ty = Some(quote!{ #proto_select_ident<#ev, #field_info> }), ref mut ev @ None => *ev = Some(field_info), @@ -310,7 +321,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { Some(quote!{ loop { - match #field_name.poll() { + match #field_name.poll(topology) { Async::Ready(#network_behaviour_action::GenerateEvent(event)) => { #handling } @@ -334,7 +345,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { // Now the magic happens. let final_quote = quote!{ - impl #impl_generics #trait_to_impl for #name #ty_generics + impl #impl_generics #trait_to_impl<#topology_generic> for #name #ty_generics #where_clause { type ProtocolsHandler = #protocols_handler_ty; @@ -367,7 +378,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream { } } - fn poll(&mut self) -> ::libp2p::futures::Async<#network_behaviour_action<::InEvent, Self::OutEvent>> { + fn poll(&mut self, topology: &mut #topology_generic) -> ::libp2p::futures::Async<#network_behaviour_action<::InEvent, Self::OutEvent>> { use libp2p::futures::prelude::*; #(#poll_stmts)* let f: ::libp2p::futures::Async<#network_behaviour_action<::InEvent, Self::OutEvent>> = #poll_method; diff --git a/misc/core-derive/tests/test.rs b/misc/core-derive/tests/test.rs index 9a68f7ed5d3..e8dd7f7cd3a 100644 --- a/misc/core-derive/tests/test.rs +++ b/misc/core-derive/tests/test.rs @@ -61,7 +61,8 @@ fn three_fields() { } } -#[test] +// TODO: fix this example ; a Rust bug prevent us from doing so +/*#[test] fn event_handler() { #[allow(dead_code)] #[derive(NetworkBehaviour)] @@ -78,7 +79,7 @@ fn event_handler() { let libp2p::identify::PeriodicIdentifyBehaviourEvent::Identified { .. } = ev; } } -} +}*/ #[test] fn custom_polling() { diff --git a/protocols/floodsub/src/layer.rs b/protocols/floodsub/src/layer.rs index 40d2ea3df66..b0f517e83bb 100644 --- a/protocols/floodsub/src/layer.rs +++ b/protocols/floodsub/src/layer.rs @@ -172,7 +172,7 @@ impl FloodsubBehaviour { } } -impl NetworkBehaviour for FloodsubBehaviour +impl NetworkBehaviour for FloodsubBehaviour where TSubstream: AsyncRead + AsyncWrite, { @@ -276,6 +276,7 @@ where fn poll( &mut self, + _: &mut TTopology, ) -> Async< NetworkBehaviourAction< ::InEvent, diff --git a/protocols/identify/src/listen_layer.rs b/protocols/identify/src/listen_layer.rs index 7ae2cfec160..5cf30f4baa3 100644 --- a/protocols/identify/src/listen_layer.rs +++ b/protocols/identify/src/listen_layer.rs @@ -61,7 +61,7 @@ impl IdentifyListen { } } -impl NetworkBehaviour for IdentifyListen +impl NetworkBehaviour for IdentifyListen where TSubstream: AsyncRead + AsyncWrite, { @@ -99,6 +99,7 @@ where fn poll( &mut self, + _: &mut TTopology, ) -> Async< NetworkBehaviourAction< ::InEvent, diff --git a/protocols/identify/src/periodic_id_layer.rs b/protocols/identify/src/periodic_id_layer.rs index b6806476f44..d8814fa7e56 100644 --- a/protocols/identify/src/periodic_id_layer.rs +++ b/protocols/identify/src/periodic_id_layer.rs @@ -44,7 +44,7 @@ impl PeriodicIdentifyBehaviour { } } -impl NetworkBehaviour for PeriodicIdentifyBehaviour +impl NetworkBehaviour for PeriodicIdentifyBehaviour where TSubstream: AsyncRead + AsyncWrite, { @@ -79,6 +79,7 @@ where fn poll( &mut self, + _: &mut TTopology, ) -> Async< NetworkBehaviourAction< ::InEvent, diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index 1bf934264eb..65759acfeb4 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -26,6 +26,7 @@ tokio-codec = "0.1" tokio-io = "0.1" tokio-timer = "0.2.6" unsigned-varint = { version = "0.2.1", features = ["codec"] } +void = "1.0" [dev-dependencies] libp2p-tcp-transport = { path = "../../transports/tcp" } diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs new file mode 100644 index 00000000000..fff9dbe44ec --- /dev/null +++ b/protocols/kad/src/behaviour.rs @@ -0,0 +1,593 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use fnv::{FnvHashMap, FnvHashSet}; +use futures::prelude::*; +use handler::{KademliaHandler, KademliaHandlerEvent, KademliaHandlerIn, KademliaRequestId}; +use libp2p_core::nodes::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction}; +use libp2p_core::{protocols_handler::ProtocolsHandler, topology::Topology, Multiaddr, PeerId}; +use multihash::Multihash; +use protocol::{KadConnectionType, KadPeer}; +use query::{QueryConfig, QueryState, QueryStatePollOut, QueryTarget}; +use rand; +use smallvec::SmallVec; +use std::{cmp::Ordering, marker::PhantomData, time::Duration}; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Interval; +use topology::KademliaTopology; + +/// Network behaviour that handles Kademlia. +pub struct Kademlia { + /// Peer ID of the local node. + local_peer_id: PeerId, + + /// All the iterative queries we are currently performing, with their ID. + active_queries: FnvHashMap, + + /// List of queries to start once we are inside `poll()`. + queries_to_starts: SmallVec<[(QueryId, QueryTarget, QueryPurpose); 8]>, + + /// List of peers the swarm is connected to. + connected_peers: FnvHashSet, + + /// Contains a list of peer IDs which we are not connected to, and an RPC query to send to them + /// once they connect. + pending_rpcs: SmallVec<[(PeerId, KademliaHandlerIn); 8]>, + + /// Identifier for the next query that we start. + next_query_id: QueryId, + + /// Requests received by a remote that we should fulfill as soon as possible. + remote_requests: SmallVec<[(PeerId, KademliaRequestId, RemoteRequestToFulfill); 4]>, + + /// List of multihashes that we're providing. + /// + /// Note that we use a `PeerId` so that we know that is uses SHA-256. The question as to how to + /// handle more hashes should eventually be resolved. + providing_keys: SmallVec<[PeerId; 8]>, + + /// Interval to send `ADD_PROVIDER` messages to everyone. + refresh_add_providers: Interval, + + /// `α` in the Kademlia reference documentation. Designates the maximum number of queries we + /// perform in parallel. + parallelism: usize, + + /// `k` in the Kademlia reference documentation. Number of results in a find node query. + num_results: usize, + + /// Timeout for each individual RPC query. + rpc_timeout: Duration, + + /// Events to return when polling. + queued_events: SmallVec<[NetworkBehaviourAction, KademliaOut>; 32]>, + + /// List of addresses to add to the topology. + add_to_topology: SmallVec<[(PeerId, Multiaddr, KadConnectionType); 32]>, + + /// List of providers to add to the topology. + add_provider: SmallVec<[(Multihash, PeerId); 32]>, + + /// Marker to pin the generics. + marker: PhantomData, +} + +/// Reason why we have this query in the list of queries. +#[derive(Debug, Clone, PartialEq, Eq)] +enum QueryPurpose { + /// The query was created for the Kademlia initialization process. + Initialization, + /// The user requested this query to be performed. It should be reported when finished. + UserRequest, + /// We should add an `ADD_PROVIDER` message to the peers of the outcome. + AddProvider(Multihash), +} + +impl Kademlia { + /// Creates a `Kademlia`. + #[inline] + pub fn new(local_peer_id: PeerId) -> Self { + Self::new_inner(local_peer_id, true) + } + + /// Creates a `Kademlia`. + /// + /// Contrary to `new`, doesn't perform the initialization queries that store our local ID into + /// the DHT. + #[inline] + pub fn without_init(local_peer_id: PeerId) -> Self { + Self::new_inner(local_peer_id, false) + } + + /// Inner implementation of the constructors. + fn new_inner(local_peer_id: PeerId, initialize: bool) -> Self { + let parallelism = 3; + + let mut behaviour = Kademlia { + local_peer_id: local_peer_id.clone(), + queued_events: SmallVec::new(), + queries_to_starts: SmallVec::new(), + active_queries: Default::default(), + connected_peers: Default::default(), + pending_rpcs: SmallVec::with_capacity(parallelism), + next_query_id: QueryId(0), + remote_requests: SmallVec::new(), + providing_keys: SmallVec::new(), + refresh_add_providers: Interval::new_interval(Duration::from_secs(60)), // TODO: constant + parallelism, + num_results: 20, + rpc_timeout: Duration::from_secs(8), + add_to_topology: SmallVec::new(), + add_provider: SmallVec::new(), + marker: PhantomData, + }; + + if initialize { + // As part of the initialization process, we start one `FIND_NODE` for each bit of the + // possible range of peer IDs. + for n in 0..256 { + let peer_id = match gen_random_id(&local_peer_id, n) { + Ok(p) => p, + Err(()) => continue, + }; + + behaviour.start_query(QueryTarget::FindPeer(peer_id), QueryPurpose::Initialization); + } + } + + behaviour + } + + /// Builds a `KadPeer` structure corresponding to the local node. + fn build_local_kad_peer(&self) -> KadPeer { + KadPeer { + node_id: self.local_peer_id.clone(), + multiaddrs: Vec::new(), // TODO: + connection_ty: KadConnectionType::Connected, + } + } + + /// Builds the answer to a request. + fn build_result(&self, query: RemoteRequestToFulfill, request_id: KademliaRequestId, topology: &mut TTopology) + -> KademliaHandlerIn + where TTopology: KademliaTopology + { + match query { + RemoteRequestToFulfill::FindNode(key) => { + let closer_peers = topology + .closest_peers(key.as_ref(), self.num_results) + .map(|peer_id| build_kad_peer(topology, peer_id)) + .collect(); + + KademliaHandlerIn::FindNodeRes { + closer_peers, + request_id, + } + }, + RemoteRequestToFulfill::GetProviders(key) => { + let closer_peers = topology + .closest_peers(&key, self.num_results) + .map(|peer_id| build_kad_peer(topology, peer_id)) + .collect(); + + let local_node_is_providing = self.providing_keys.iter().any(|k| k.as_ref() == &key); + + let provider_peers = topology + .get_providers(&key) + .map(|peer_id| build_kad_peer(topology, peer_id)) + .chain(if local_node_is_providing { + Some(self.build_local_kad_peer()) + } else { + None + }.into_iter()) + .collect(); + + KademliaHandlerIn::GetProvidersRes { + closer_peers, + provider_peers, + request_id, + } + }, + } + } +} + +impl Kademlia { + /// Starts an iterative `FIND_NODE` request. + /// + /// This will eventually produce an event containing the nodes of the DHT closest to the + /// requested `PeerId`. + #[inline] + pub fn find_node(&mut self, peer_id: PeerId) { + self.start_query(QueryTarget::FindPeer(peer_id), QueryPurpose::UserRequest); + } + + /// Starts an iterative `GET_PROVIDERS` request. + #[inline] + pub fn get_providers(&mut self, key: Multihash) { + self.start_query(QueryTarget::GetProviders(key), QueryPurpose::UserRequest); + } + + /// Register the local node as the provider for the given key. + /// + /// This will periodically send `ADD_PROVIDER` messages to the nodes closest to the key. When + /// someone performs a `GET_PROVIDERS` iterative request on the DHT, our local node will be + /// returned as part of the results. + /// + /// The actual meaning of *providing* the value of a key is not defined, and is specific to + /// the value whose key is the hash. + pub fn add_providing(&mut self, key: PeerId) { + if !self.providing_keys.iter().any(|k| k == &key) { + self.providing_keys.push(key); + } + unimplemented!() // TODO: + } + + /// Cancels a registration done with `add_providing`. + /// + /// There doesn't exist any "remove provider" message to broadcast on the network, therefore we + /// will still be registered as a provider in the DHT for as long as the timeout didn't expire. + pub fn remove_providing(&mut self, key: &Multihash) { + if let Some(position) = self.providing_keys.iter().position(|k| k.as_ref() == key) { + self.providing_keys.remove(position); + } + unimplemented!() // TODO: + } + + /// Internal function that starts a query. + fn start_query(&mut self, target: QueryTarget, purpose: QueryPurpose) { + let query_id = self.next_query_id.clone(); + self.next_query_id.0 += 1; + self.queries_to_starts.push((query_id, target, purpose)); + } +} + +impl NetworkBehaviour for Kademlia +where + TSubstream: AsyncRead + AsyncWrite, + TTopology: KademliaTopology, +{ + type ProtocolsHandler = KademliaHandler; + type OutEvent = KademliaOut; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + KademliaHandler::dial_and_listen() + } + + fn inject_connected(&mut self, id: PeerId, _: ConnectedPoint) { + if let Some(pos) = self.pending_rpcs.iter().position(|(p, _)| p == &id) { + let (_, rpc) = self.pending_rpcs.remove(pos); + self.queued_events.push(NetworkBehaviourAction::SendEvent { + peer_id: id.clone(), + event: rpc, + }); + } + + self.connected_peers.insert(id); + } + + fn inject_disconnected(&mut self, id: &PeerId, _: ConnectedPoint) { + let was_in = self.connected_peers.remove(id); + debug_assert!(was_in); + + for (query, _) in self.active_queries.values_mut() { + query.inject_rpc_error(id); + } + } + + fn inject_node_event(&mut self, source: PeerId, event: KademliaHandlerEvent) { + match event { + KademliaHandlerEvent::FindNodeReq { key, request_id } => { + self.remote_requests.push((source, request_id, RemoteRequestToFulfill::FindNode(key))); + return; + } + KademliaHandlerEvent::FindNodeRes { + closer_peers, + user_data, + } => { + // It is possible that we obtain a response for a query that has finished, which is + // why we may not find an entry in `self.active_queries`. + for peer in closer_peers.iter() { + for addr in peer.multiaddrs.iter() { + self.add_to_topology + .push((peer.node_id.clone(), addr.clone(), peer.connection_ty)); + } + } + if let Some((query, _)) = self.active_queries.get_mut(&user_data) { + query.inject_rpc_result(&source, closer_peers.into_iter().map(|kp| kp.node_id)) + } + } + KademliaHandlerEvent::GetProvidersReq { key, request_id } => { + self.remote_requests.push((source, request_id, RemoteRequestToFulfill::GetProviders(key))); + return; + } + KademliaHandlerEvent::GetProvidersRes { + closer_peers, + provider_peers, + user_data, + } => { + // TODO: provider peers? how does that work? + // It is possible that we obtain a response for a query that has finished, which is + // why we may not find an entry in `self.active_queries`. + for peer in closer_peers.iter() { + for addr in peer.multiaddrs.iter() { + self.add_to_topology + .push((peer.node_id.clone(), addr.clone(), peer.connection_ty)); + } + } + if let Some((query, _)) = self.active_queries.get_mut(&user_data) { + query.inject_rpc_result(&source, closer_peers.into_iter().map(|kp| kp.node_id)) + } + } + KademliaHandlerEvent::QueryError { error, user_data } => { + // It is possible that we obtain a response for a query that has finished, which is + // why we may not find an entry in `self.active_queries`. + if let Some((query, _)) = self.active_queries.get_mut(&user_data) { + query.inject_rpc_error(&source) + } + } + KademliaHandlerEvent::AddProvider { key, provider_peer } => { + for addr in provider_peer.multiaddrs.iter() { + self.add_to_topology + .push((provider_peer.node_id.clone(), addr.clone(), provider_peer.connection_ty)); + } + self.add_provider.push((key, provider_peer.node_id)); + return; + } + }; + } + + fn poll( + &mut self, + topology: &mut TTopology, + ) -> Async< + NetworkBehaviourAction< + ::InEvent, + Self::OutEvent, + >, + > { + // Flush the changes to the topology that we want to make. + for (peer_id, addr, connection_ty) in self.add_to_topology.drain() { + topology.add_kad_discovered_address(&peer_id, addr, connection_ty); + } + self.add_to_topology.shrink_to_fit(); + for (key, provider) in self.add_provider.drain() { + topology.add_provider(key, provider); + } + self.add_provider.shrink_to_fit(); + + // Handle `refresh_add_providers`. + match self.refresh_add_providers.poll() { + Ok(Async::NotReady) => {}, + Ok(Async::Ready(Some(_))) => { + for provided in self.providing_keys.clone().into_iter() { + let purpose = QueryPurpose::AddProvider(provided.as_ref().clone()); + self.start_query(QueryTarget::FindPeer(provided), purpose); + } + }, + Ok(Async::Ready(None)) | Err(_) => panic!(), // TODO: + } + + // Start queries that are waiting to start. + for (query_id, query_target, query_purpose) in self.queries_to_starts.drain() { + let known_closest_peers = topology + .closest_peers(query_target.as_hash(), self.num_results); + self.active_queries.insert( + query_id, + ( + QueryState::new(QueryConfig { + target: query_target, + parallelism: self.parallelism, + num_results: self.num_results, + rpc_timeout: self.rpc_timeout, + known_closest_peers, + }), + query_purpose, + ), + ); + } + self.queries_to_starts.shrink_to_fit(); + + // Handle remote queries. + if !self.remote_requests.is_empty() { + let (peer_id, request_id, query) = self.remote_requests.remove(0); + let result = self.build_result(query, request_id, topology); + return Async::Ready(NetworkBehaviourAction::SendEvent { + peer_id, + event: result, + }); + } + + loop { + // Handle events queued by other parts of this struct + if !self.queued_events.is_empty() { + return Async::Ready(self.queued_events.remove(0)); + } + self.queued_events.shrink_to_fit(); + + // If iterating finds a query that is finished, stores it here and stops looping. + let mut finished_query = None; + + 'queries_iter: for (&query_id, (query, _)) in self.active_queries.iter_mut() { + loop { + match query.poll() { + Async::Ready(QueryStatePollOut::Finished) => { + finished_query = Some(query_id); + break 'queries_iter; + } + Async::Ready(QueryStatePollOut::SendRpc { + peer_id, + query_target, + }) => { + let rpc = query_target.to_rpc_request(query_id); + if self.connected_peers.contains(&peer_id) { + return Async::Ready(NetworkBehaviourAction::SendEvent { + peer_id: peer_id.clone(), + event: rpc, + }); + } else { + self.pending_rpcs.push((peer_id.clone(), rpc)); + return Async::Ready(NetworkBehaviourAction::DialPeer { + peer_id: peer_id.clone(), + }); + } + } + Async::Ready(QueryStatePollOut::CancelRpc { peer_id }) => { + // We don't cancel if the RPC has already been sent out. + self.pending_rpcs.retain(|(id, _)| id != peer_id); + } + Async::NotReady => break, + } + } + } + + if let Some(finished_query) = finished_query { + let (query, purpose) = self + .active_queries + .remove(&finished_query) + .expect("finished_query was gathered when iterating active_queries ; qed"); + match purpose { + QueryPurpose::Initialization => {}, + QueryPurpose::UserRequest => { + let event = match query.target().clone() { + QueryTarget::FindPeer(key) => { + KademliaOut::FindNodeResult { + key, + closer_peers: query.into_closest_peers().collect(), + } + }, + QueryTarget::GetProviders(key) => { + KademliaOut::GetProvidersResult { + key, + closer_peers: query.into_closest_peers().collect(), + provider_peers: Vec::new(), // FIXME: + } + }, + QueryTarget::FindValue(key) => { + unimplemented!() // TODO: + }, + }; + + break Async::Ready(NetworkBehaviourAction::GenerateEvent(event)); + }, + QueryPurpose::AddProvider(key) => { + for closest in query.into_closest_peers() { + let event = NetworkBehaviourAction::SendEvent { + peer_id: closest, + event: KademliaHandlerIn::AddProvider { + key: key.clone(), + provider_peer: self.build_local_kad_peer(), + }, + }; + + self.queued_events.push(event); + } + }, + } + } else { + break Async::NotReady; + } + } + } +} + +/// Output event of the `Kademlia` behaviour. +#[derive(Debug, Clone)] +pub enum KademliaOut { + /// Result of a `FIND_NODE` iterative query. + FindNodeResult { + /// The key that we looked for in the query. + key: PeerId, + /// List of peers ordered from closest to furthest away. + closer_peers: Vec, + }, + + /// Result of a `GET_PROVIDERS` iterative query. + GetProvidersResult { + /// The key that we looked for in the query. + key: Multihash, + /// The peers that are providing the requested key. + provider_peers: Vec, + /// List of peers ordered from closest to furthest away. + closer_peers: Vec, + }, +} + +/// Opaque type. Each query that we start gets a unique number. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct QueryId(usize); + +/// Request received by a remote that we should fulfill as soon as possible. +#[derive(Debug, Clone, PartialEq, Eq)] +enum RemoteRequestToFulfill { + /// `FIND_NODE` request. + FindNode(PeerId), + /// `GET_PROVIDERS` request. + GetProviders(Multihash), + // TODO: `FIND_VALUE` +} + +// Generates a random `PeerId` that belongs to the given bucket. +// +// Returns an error if `bucket_num` is out of range. +fn gen_random_id(my_id: &PeerId, bucket_num: usize) -> Result { + let my_id_len = my_id.as_bytes().len(); + + // TODO: this 2 is magic here; it is the length of the hash of the multihash + let bits_diff = bucket_num + 1; + if bits_diff > 8 * (my_id_len - 2) { + return Err(()); + } + + let mut random_id = [0; 64]; + for byte in 0..my_id_len { + match byte.cmp(&(my_id_len - bits_diff / 8 - 1)) { + Ordering::Less => { + random_id[byte] = my_id.as_bytes()[byte]; + } + Ordering::Equal => { + let mask: u8 = (1 << (bits_diff % 8)) - 1; + random_id[byte] = (my_id.as_bytes()[byte] & !mask) | (rand::random::() & mask); + } + Ordering::Greater => { + random_id[byte] = rand::random(); + } + } + } + + let peer_id = PeerId::from_bytes(random_id[..my_id_len].to_owned()) + .expect("randomly-generated peer ID should always be valid"); + Ok(peer_id) +} + +/// Builds a `KadPeer` struct corresponding to the given `PeerId`. +/// +/// > **Note**: This is just a convenience function that doesn't do anything note-worthy. +fn build_kad_peer(topology: &mut TTopology, peer_id: PeerId) -> KadPeer +where TTopology: Topology +{ + let multiaddrs = topology.addresses_of_peer(&peer_id); + + KadPeer { + node_id: peer_id, + multiaddrs, + connection_ty: KadConnectionType::NotConnected, // TODO: + } +} diff --git a/protocols/kad/src/handler.rs b/protocols/kad/src/handler.rs new file mode 100644 index 00000000000..d0154b28e0c --- /dev/null +++ b/protocols/kad/src/handler.rs @@ -0,0 +1,772 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::prelude::*; +use libp2p_core::protocols_handler::{ProtocolsHandler, ProtocolsHandlerEvent}; +use libp2p_core::{upgrade, either::EitherOutput, InboundUpgrade, OutboundUpgrade, PeerId}; +use multihash::Multihash; +use protocol::{ + KadInStreamSink, KadOutStreamSink, KadPeer, KadRequestMsg, KadResponseMsg, + KademliaProtocolConfig, +}; +use std::io; +use tokio_io::{AsyncRead, AsyncWrite}; + +/// Protocol handler that handles Kademlia communications with the remote. +/// +/// The handler will automatically open a Kademlia substream with the remote for each request we +/// make. +/// +/// It also handles requests made by the remote. +pub struct KademliaHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ + /// Configuration for the Kademlia protocol. + config: KademliaProtocolConfig, + + /// If true, we are trying to shut down the existing Kademlia substream and should refuse any + /// incoming connection. + shutting_down: bool, + + /// If false, we always refuse incoming Kademlia substreams. + allow_listening: bool, + + /// Next unique ID of a connection. + next_connec_unique_id: UniqueConnecId, + + /// List of active substreams with the state they are in. + substreams: Vec>, +} + +/// State of an active substream, opened either by us or by the remote. +enum SubstreamState +where + TSubstream: AsyncRead + AsyncWrite, +{ + /// We haven't started opening the outgoing substream yet. + /// Contains the request we want to send, and the user data if we expect an answer. + OutPendingOpen(KadRequestMsg, Option), + /// We are waiting for the outgoing substream to be upgraded. + /// Contains the request we want to send, and the user data if we expect an answer. + OutPendingUpgrade(KadRequestMsg, Option), + /// Waiting to send a message to the remote. + OutPendingSend( + KadOutStreamSink, + KadRequestMsg, + Option, + ), + /// Waiting to send a message to the remote. + /// Waiting to flush the substream so that the data arrives to the remote. + OutPendingFlush(KadOutStreamSink, Option), + /// Waiting for an answer back from the remote. + // TODO: add timeout + OutWaitingAnswer(KadOutStreamSink, TUserData), + /// An error happened on the substream and we should report the error to the user. + OutReportError(io::Error, TUserData), + /// The substream is being closed. + OutClosing(KadOutStreamSink), + /// Waiting for a request from the remote. + InWaitingMessage(UniqueConnecId, KadInStreamSink), + /// Waiting for the user to send a `KademliaHandlerIn` event containing the response. + InWaitingUser(UniqueConnecId, KadInStreamSink), + /// Waiting to send an answer back to the remote. + InPendingSend(UniqueConnecId, KadInStreamSink, KadResponseMsg), + /// Waiting to flush an answer back to the remote. + InPendingFlush(UniqueConnecId, KadInStreamSink), + /// The substream is being closed. + InClosing(KadInStreamSink), +} + +impl SubstreamState +where + TSubstream: AsyncRead + AsyncWrite, +{ + /// Consumes this state and tries to close the substream. + /// + /// If the substream is not ready to be closed, returns it back. + fn try_close(self) -> AsyncSink { + match self { + SubstreamState::OutPendingOpen(_, _) + | SubstreamState::OutPendingUpgrade(_, _) + | SubstreamState::OutReportError(_, _) => AsyncSink::Ready, + SubstreamState::OutPendingSend(mut stream, _, _) + | SubstreamState::OutPendingFlush(mut stream, _) + | SubstreamState::OutWaitingAnswer(mut stream, _) + | SubstreamState::OutClosing(mut stream) => match stream.close() { + Ok(Async::Ready(())) | Err(_) => AsyncSink::Ready, + Ok(Async::NotReady) => AsyncSink::NotReady(SubstreamState::OutClosing(stream)), + }, + SubstreamState::InWaitingMessage(_, mut stream) + | SubstreamState::InWaitingUser(_, mut stream) + | SubstreamState::InPendingSend(_, mut stream, _) + | SubstreamState::InPendingFlush(_, mut stream) + | SubstreamState::InClosing(mut stream) => match stream.close() { + Ok(Async::Ready(())) | Err(_) => AsyncSink::Ready, + Ok(Async::NotReady) => AsyncSink::NotReady(SubstreamState::InClosing(stream)), + }, + } + } +} + +/// Event produced by the Kademlia handler. +#[derive(Debug)] +pub enum KademliaHandlerEvent { + /// Request for the list of nodes whose IDs are the closest to `key`. The number of nodes + /// returned is not specified, but should be around 20. + FindNodeReq { + /// Identifier of the node. + key: PeerId, + /// Identifier of the request. Needs to be passed back when answering. + request_id: KademliaRequestId, + }, + + /// Response to an `KademliaHandlerIn::FindNodeReq`. + FindNodeRes { + /// Results of the request. + closer_peers: Vec, + /// The user data passed to the `FindNodeReq`. + user_data: TUserData, + }, + + /// Same as `FindNodeReq`, but should also return the entries of the local providers list for + /// this key. + GetProvidersReq { + /// Identifier being searched. + key: Multihash, + /// Identifier of the request. Needs to be passed back when answering. + request_id: KademliaRequestId, + }, + + /// Response to an `KademliaHandlerIn::GetProvidersReq`. + GetProvidersRes { + /// Nodes closest to the key. + closer_peers: Vec, + /// Known providers for this key. + provider_peers: Vec, + /// The user data passed to the `GetProvidersReq`. + user_data: TUserData, + }, + + /// An error happened when performing a query. + QueryError { + /// The error that happened. + error: io::Error, + /// The user data passed to the query. + user_data: TUserData, + }, + + /// The remote indicates that this list of providers is known for this key. + AddProvider { + /// Key for which we should add providers. + key: Multihash, + /// Known provider for this key. + provider_peer: KadPeer, + }, +} + +/// Event to send to the handler. +pub enum KademliaHandlerIn { + /// Request for the list of nodes whose IDs are the closest to `key`. The number of nodes + /// returned is not specified, but should be around 20. + FindNodeReq { + /// Identifier of the node. + key: PeerId, + /// Custom user data. Passed back in the out event when the results arrive. + user_data: TUserData, + }, + + /// Response to a `FindNodeReq`. + FindNodeRes { + /// Results of the request. + closer_peers: Vec, + /// Identifier of the request that was made by the remote. + /// + /// It is a logic error to use an id of the handler of a different node. + request_id: KademliaRequestId, + }, + + /// Same as `FindNodeReq`, but should also return the entries of the local providers list for + /// this key. + GetProvidersReq { + /// Identifier being searched. + key: Multihash, + /// Custom user data. Passed back in the out event when the results arrive. + user_data: TUserData, + }, + + /// Response to a `GetProvidersReq`. + GetProvidersRes { + /// Nodes closest to the key. + closer_peers: Vec, + /// Known providers for this key. + provider_peers: Vec, + /// Identifier of the request that was made by the remote. + /// + /// It is a logic error to use an id of the handler of a different node. + request_id: KademliaRequestId, + }, + + /// Indicates that this provider is known for this key. + /// + /// The API of the handler doesn't expose any event that allows you to know whether this + /// succeeded. + AddProvider { + /// Key for which we should add providers. + key: Multihash, + /// Known provider for this key. + provider_peer: KadPeer, + }, +} + +/// Unique identifier for a request. Must be passed back in order to answer a request from +/// the remote. +/// +/// We don't implement `Clone` on purpose, in order to prevent users from answering the same +/// request twice. +#[derive(Debug, PartialEq, Eq)] +pub struct KademliaRequestId { + /// Unique identifier for an incoming connection. + connec_unique_id: UniqueConnecId, +} + +/// Unique identifier for a connection. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct UniqueConnecId(u64); + +impl KademliaHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ + /// Create a `KademliaHandler` that only allows sending messages to the remote but denying + /// incoming connections. + #[inline] + pub fn dial_only() -> Self { + KademliaHandler::with_allow_listening(false) + } + + /// Create a `KademliaHandler` that only allows sending messages but also receive incoming + /// requests. + /// + /// The `Default` trait implementation wraps around this function. + #[inline] + pub fn dial_and_listen() -> Self { + KademliaHandler::with_allow_listening(true) + } + + fn with_allow_listening(allow_listening: bool) -> Self { + KademliaHandler { + config: Default::default(), + shutting_down: false, + allow_listening, + next_connec_unique_id: UniqueConnecId(0), + substreams: Vec::new(), + } + } +} + +impl Default for KademliaHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ + #[inline] + fn default() -> Self { + KademliaHandler::dial_and_listen() + } +} + +impl ProtocolsHandler for KademliaHandler +where + TSubstream: AsyncRead + AsyncWrite, + TUserData: Clone, +{ + type InEvent = KademliaHandlerIn; + type OutEvent = KademliaHandlerEvent; + type Substream = TSubstream; + type InboundProtocol = upgrade::EitherUpgrade; + type OutboundProtocol = KademliaProtocolConfig; + // Message of the request to send to the remote, and user data if we expect an answer. + type OutboundOpenInfo = (KadRequestMsg, Option); + + #[inline] + fn listen_protocol(&self) -> Self::InboundProtocol { + if self.allow_listening { + upgrade::EitherUpgrade::A(self.config) + } else { + upgrade::EitherUpgrade::B(upgrade::DeniedUpgrade) + } + } + + fn inject_fully_negotiated_outbound( + &mut self, + protocol: >::Output, + (msg, user_data): Self::OutboundOpenInfo, + ) { + if self.shutting_down { + return; + } + + self.substreams + .push(SubstreamState::OutPendingSend(protocol, msg, user_data)); + } + + fn inject_fully_negotiated_inbound( + &mut self, + protocol: >::Output, + ) { + // If `self.allow_listening` is false, then we produced a `DeniedUpgrade` and `protocol` + // is a `Void`. + let protocol = match protocol { + EitherOutput::First(p) => p, + EitherOutput::Second(p) => void::unreachable(p), + }; + + if self.shutting_down { + return; + } + + debug_assert!(self.allow_listening); + let connec_unique_id = self.next_connec_unique_id; + self.next_connec_unique_id.0 += 1; + self.substreams + .push(SubstreamState::InWaitingMessage(connec_unique_id, protocol)); + } + + #[inline] + fn inject_event(&mut self, message: KademliaHandlerIn) { + match message { + KademliaHandlerIn::FindNodeReq { key, user_data } => { + let msg = KadRequestMsg::FindNode { key: key.clone() }; + self.substreams + .push(SubstreamState::OutPendingOpen(msg, Some(user_data.clone()))); + } + KademliaHandlerIn::FindNodeRes { + closer_peers, + request_id, + } => { + let pos = self.substreams.iter().position(|state| match state { + SubstreamState::InWaitingUser(ref conn_id, _) + if conn_id == &request_id.connec_unique_id => + { + true + } + _ => false, + }); + + if let Some(pos) = pos { + let (conn_id, substream) = match self.substreams.remove(pos) { + SubstreamState::InWaitingUser(conn_id, substream) => (conn_id, substream), + _ => unreachable!(), + }; + + let msg = KadResponseMsg::FindNode { + closer_peers: closer_peers.clone(), + }; + self.substreams + .push(SubstreamState::InPendingSend(conn_id, substream, msg)); + } + } + KademliaHandlerIn::GetProvidersReq { key, user_data } => { + let msg = KadRequestMsg::GetProviders { key: key.clone() }; + self.substreams + .push(SubstreamState::OutPendingOpen(msg, Some(user_data.clone()))); + } + KademliaHandlerIn::GetProvidersRes { + closer_peers, + provider_peers, + request_id, + } => { + let pos = self.substreams.iter().position(|state| match state { + SubstreamState::InWaitingUser(ref conn_id, _) + if conn_id == &request_id.connec_unique_id => + { + true + } + _ => false, + }); + + if let Some(pos) = pos { + let (conn_id, substream) = match self.substreams.remove(pos) { + SubstreamState::InWaitingUser(conn_id, substream) => (conn_id, substream), + _ => unreachable!(), + }; + + let msg = KadResponseMsg::GetProviders { + closer_peers: closer_peers.clone(), + provider_peers: provider_peers.clone(), + }; + self.substreams + .push(SubstreamState::InPendingSend(conn_id, substream, msg)); + } + } + KademliaHandlerIn::AddProvider { key, provider_peer } => { + let msg = KadRequestMsg::AddProvider { + key: key.clone(), + provider_peer: provider_peer.clone(), + }; + self.substreams + .push(SubstreamState::OutPendingOpen(msg, None)); + } + } + } + + #[inline] + fn inject_inbound_closed(&mut self) {} + + #[inline] + fn inject_dial_upgrade_error( + &mut self, + (_, user_data): Self::OutboundOpenInfo, + error: io::Error, + ) { + // TODO: cache the fact that the remote doesn't support kademlia at all, so that we don't + // continue trying + if let Some(user_data) = user_data { + self.substreams + .push(SubstreamState::OutReportError(error, user_data)); + } + } + + #[inline] + fn shutdown(&mut self) { + self.shutting_down = true; + } + + fn poll( + &mut self, + ) -> Poll< + Option< + ProtocolsHandlerEvent, + >, + io::Error, + > { + // Special case if shutting down. + if self.shutting_down { + for n in (0..self.substreams.len()).rev() { + match self.substreams.swap_remove(n).try_close() { + AsyncSink::Ready => (), + AsyncSink::NotReady(stream) => self.substreams.push(stream), + } + } + + if self.substreams.is_empty() { + return Ok(Async::Ready(None)); + } else { + return Ok(Async::NotReady); + } + } + + // We remove each element from `substreams` one by one and add them back. + for n in (0..self.substreams.len()).rev() { + let mut substream = self.substreams.swap_remove(n); + + loop { + match advance_substream(substream, self.config) { + (Some(new_state), Some(event), _) => { + self.substreams.push(new_state); + return Ok(Async::Ready(Some(event))); + } + (None, Some(event), _) => { + return Ok(Async::Ready(Some(event))); + } + (Some(new_state), None, false) => { + self.substreams.push(new_state); + break; + } + (Some(new_state), None, true) => { + substream = new_state; + continue; + } + (None, None, _) => { + break; + } + } + } + } + + Ok(Async::NotReady) + } +} + +/// Advances one substream. +/// +/// Returns the new state for that substream, an event to generate, and whether the substream +/// should be polled again. +fn advance_substream( + state: SubstreamState, + upgrade: KademliaProtocolConfig, +) -> ( + Option>, + Option< + ProtocolsHandlerEvent< + KademliaProtocolConfig, + (KadRequestMsg, Option), + KademliaHandlerEvent, + >, + >, + bool, +) +where + TSubstream: AsyncRead + AsyncWrite, +{ + match state { + SubstreamState::OutPendingOpen(msg, user_data) => { + let ev = ProtocolsHandlerEvent::OutboundSubstreamRequest { + upgrade, + info: (msg, user_data), + }; + (None, Some(ev), false) + } + SubstreamState::OutPendingUpgrade(msg, user_data) => ( + Some(SubstreamState::OutPendingUpgrade(msg, user_data)), + None, + false, + ), + SubstreamState::OutPendingSend(mut substream, msg, user_data) => { + match substream.start_send(msg) { + Ok(AsyncSink::Ready) => ( + Some(SubstreamState::OutPendingFlush(substream, user_data)), + None, + true, + ), + Ok(AsyncSink::NotReady(msg)) => ( + Some(SubstreamState::OutPendingSend(substream, msg, user_data)), + None, + false, + ), + Err(error) => { + let event = if let Some(user_data) = user_data { + let ev = KademliaHandlerEvent::QueryError { error, user_data }; + Some(ProtocolsHandlerEvent::Custom(ev)) + } else { + None + }; + + (None, event, false) + } + } + } + SubstreamState::OutPendingFlush(mut substream, user_data) => { + match substream.poll_complete() { + Ok(Async::Ready(())) => { + if let Some(user_data) = user_data { + ( + Some(SubstreamState::OutWaitingAnswer(substream, user_data)), + None, + true, + ) + } else { + (Some(SubstreamState::OutClosing(substream)), None, true) + } + } + Ok(Async::NotReady) => ( + Some(SubstreamState::OutPendingFlush(substream, user_data)), + None, + false, + ), + Err(error) => { + let event = if let Some(user_data) = user_data { + let ev = KademliaHandlerEvent::QueryError { error, user_data }; + Some(ProtocolsHandlerEvent::Custom(ev)) + } else { + None + }; + + (None, event, false) + } + } + } + SubstreamState::OutWaitingAnswer(mut substream, user_data) => match substream.poll() { + Ok(Async::Ready(Some(msg))) => { + let new_state = SubstreamState::OutClosing(substream); + let event = process_kad_response(msg, user_data); + ( + Some(new_state), + Some(ProtocolsHandlerEvent::Custom(event)), + true, + ) + } + Ok(Async::NotReady) => ( + Some(SubstreamState::OutWaitingAnswer(substream, user_data)), + None, + false, + ), + Err(error) => { + let event = KademliaHandlerEvent::QueryError { error, user_data }; + (None, Some(ProtocolsHandlerEvent::Custom(event)), false) + } + Ok(Async::Ready(None)) => { + let error = io::Error::new(io::ErrorKind::Other, "unexpected EOF"); + let event = KademliaHandlerEvent::QueryError { error, user_data }; + (None, Some(ProtocolsHandlerEvent::Custom(event)), false) + } + }, + SubstreamState::OutReportError(error, user_data) => { + let event = KademliaHandlerEvent::QueryError { error, user_data }; + (None, Some(ProtocolsHandlerEvent::Custom(event)), false) + } + SubstreamState::OutClosing(mut stream) => match stream.close() { + Ok(Async::Ready(())) => (None, None, false), + Ok(Async::NotReady) => (Some(SubstreamState::OutClosing(stream)), None, false), + Err(_) => (None, None, false), + }, + SubstreamState::InWaitingMessage(id, mut substream) => match substream.poll() { + Ok(Async::Ready(Some(msg))) => { + if let Ok(ev) = process_kad_request(msg, id) { + ( + Some(SubstreamState::InWaitingUser(id, substream)), + Some(ProtocolsHandlerEvent::Custom(ev)), + false, + ) + } else { + (Some(SubstreamState::InClosing(substream)), None, true) + } + } + Ok(Async::NotReady) => ( + Some(SubstreamState::InWaitingMessage(id, substream)), + None, + false, + ), + Ok(Async::Ready(None)) | Err(_) => (None, None, false), + }, + SubstreamState::InWaitingUser(id, substream) => ( + Some(SubstreamState::InWaitingUser(id, substream)), + None, + false, + ), + SubstreamState::InPendingSend(id, mut substream, msg) => match substream.start_send(msg) { + Ok(AsyncSink::Ready) => ( + Some(SubstreamState::InPendingFlush(id, substream)), + None, + true, + ), + Ok(AsyncSink::NotReady(msg)) => ( + Some(SubstreamState::InPendingSend(id, substream, msg)), + None, + false, + ), + Err(_) => (None, None, false), + }, + SubstreamState::InPendingFlush(id, mut substream) => match substream.poll_complete() { + Ok(Async::Ready(())) => ( + Some(SubstreamState::InWaitingMessage(id, substream)), + None, + true, + ), + Ok(Async::NotReady) => ( + Some(SubstreamState::InPendingFlush(id, substream)), + None, + false, + ), + Err(_) => (None, None, false), + }, + SubstreamState::InClosing(mut stream) => match stream.close() { + Ok(Async::Ready(())) => (None, None, false), + Ok(Async::NotReady) => (Some(SubstreamState::InClosing(stream)), None, false), + Err(_) => (None, None, false), + }, + } +} + +/// Processes a Kademlia message that's expected to be a request from a remote. +fn process_kad_request( + event: KadRequestMsg, + connec_unique_id: UniqueConnecId, +) -> Result, io::Error> { + match event { + KadRequestMsg::Ping => { + // TODO: implement + Err(io::Error::new( + io::ErrorKind::InvalidData, + "the PING Kademlia message is not implemented", + )) + } + KadRequestMsg::PutValue { .. } => { + // TODO: implement + Err(io::Error::new( + io::ErrorKind::InvalidData, + "the PUT_VALUE Kademlia message is not implemented", + )) + } + + KadRequestMsg::FindNode { key } => Ok(KademliaHandlerEvent::FindNodeReq { + key, + request_id: KademliaRequestId { connec_unique_id }, + }), + KadRequestMsg::GetValue { .. } => { + // TODO: implement + Err(io::Error::new( + io::ErrorKind::InvalidData, + "the GET_VALUE Kademlia message is not implemented", + )) + } + KadRequestMsg::GetProviders { key } => Ok(KademliaHandlerEvent::GetProvidersReq { + key, + request_id: KademliaRequestId { connec_unique_id }, + }), + KadRequestMsg::AddProvider { key, provider_peer } => { + Ok(KademliaHandlerEvent::AddProvider { key, provider_peer }) + } + } +} + +/// Process a Kademlia message that's supposed to be a response to one of our requests. +fn process_kad_response( + event: KadResponseMsg, + user_data: TUserData, +) -> KademliaHandlerEvent { + // TODO: must check that the response corresponds to the request + match event { + KadResponseMsg::Pong => { + // We never send out pings. + let err = io::Error::new( + io::ErrorKind::InvalidData, + "received unexpected PONG message", + ); + KademliaHandlerEvent::QueryError { + error: err, + user_data, + } + } + KadResponseMsg::GetValue { .. } => { + // TODO: implement + let err = io::Error::new( + io::ErrorKind::InvalidData, + "received unexpected GET_VALUE response", + ); + KademliaHandlerEvent::QueryError { + error: err, + user_data, + } + } + KadResponseMsg::FindNode { closer_peers } => KademliaHandlerEvent::FindNodeRes { + closer_peers, + user_data, + }, + KadResponseMsg::GetProviders { + closer_peers, + provider_peers, + } => KademliaHandlerEvent::GetProvidersRes { + closer_peers, + provider_peers, + user_data, + }, + } +} diff --git a/protocols/kad/src/high_level.rs b/protocols/kad/src/high_level.rs deleted file mode 100644 index 9e5f04f8c00..00000000000 --- a/protocols/kad/src/high_level.rs +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use fnv::FnvHashSet; -use futures::{future, Future, IntoFuture, stream, Stream}; -use kad_server::KadConnecController; -use kbucket::{KBucketsTable, KBucketsPeerId}; -use libp2p_core::PeerId; -use log::{debug, trace}; -use protocol; -use rand; -use smallvec::SmallVec; -use std::cmp::Ordering; -use std::io::{Error as IoError, ErrorKind as IoErrorKind}; -use std::mem; -use std::time::Duration; -use tokio_timer::Timeout; - -/// Prototype for a future Kademlia protocol running on a socket. -#[derive(Debug, Clone)] -pub struct KadSystemConfig { - /// Degree of parallelism on the network. Often called `alpha` in technical papers. - /// No more than this number of remotes will be used at a given time for any given operation. - // TODO: ^ share this number between operations? or does each operation use `alpha` remotes? - pub parallelism: u32, - /// Id of the local peer. - pub local_peer_id: PeerId, - /// List of peers initially known. - pub known_initial_peers: I, - /// Duration after which a node in the k-buckets needs to be pinged again. - pub kbuckets_timeout: Duration, - /// When contacting a node, duration after which we consider it unresponsive. - pub request_timeout: Duration, -} - -/// System that drives the whole Kademlia process. -pub struct KadSystem { - // The actual DHT. - kbuckets: KBucketsTable, - // Same as in the config. - parallelism: u32, - // Same as in the config. - request_timeout: Duration, -} - -/// Event that happens during a query. -#[derive(Debug, Clone)] -pub enum KadQueryEvent { - /// Learned about new mutiaddresses for the given peers. - PeersReported(Vec), - /// Finished the processing of the query. Contains the result. - Finished(TOut), -} - -impl KadSystem { - /// Starts a new Kademlia system. - /// - /// Also produces a `Future` that drives a Kademlia initialization process. - /// This future should be driven to completion by the caller. - pub fn start<'a, F, Fut>(config: KadSystemConfig>, access: F) - -> (KadSystem, impl Future + 'a) - where F: FnMut(&PeerId) -> Fut + Send + Clone + 'a, - Fut: IntoFuture + 'a, - Fut::Future: Send, - { - let system = KadSystem::without_init(config); - let init_future = system.perform_initialization(access); - (system, init_future) - } - - /// Same as `start`, but doesn't perform the initialization process. - pub fn without_init(config: KadSystemConfig>) -> KadSystem { - let kbuckets = KBucketsTable::new(config.local_peer_id.clone(), config.kbuckets_timeout); - for peer in config.known_initial_peers { - let _ = kbuckets.update(peer, ()); - } - - let system = KadSystem { - kbuckets: kbuckets, - parallelism: config.parallelism, - request_timeout: config.request_timeout, - }; - - system - } - - /// Starts an initialization process. - pub fn perform_initialization<'a, F, Fut>(&self, access: F) -> impl Future + 'a - where F: FnMut(&PeerId) -> Fut + Send + Clone + 'a, - Fut: IntoFuture + 'a, - Fut::Future: Send, - { - let futures: Vec<_> = (0..256) // TODO: 256 is arbitrary - .map(|n| { - refresh(n, access.clone(), &self.kbuckets, - self.parallelism as usize, self.request_timeout) - }) - .map(|stream| stream.for_each(|_| Ok(()))) - .collect(); - - future::loop_fn(futures, |futures| { - if futures.is_empty() { - let fut = future::ok(future::Loop::Break(())); - return future::Either::A(fut); - } - - let fut = future::select_all(futures) - .map_err(|(err, _, _)| err) - .map(|(_, _, rest)| future::Loop::Continue(rest)); - future::Either::B(fut) - }) - } - - /// Updates the k-buckets with the specific peer. - /// - /// Should be called whenever we receive a message from a peer. - pub fn update_kbuckets(&self, peer: PeerId) { - // TODO: ping system - let _ = self.kbuckets.update(peer, ()); - } - - /// Returns the local peer ID, as passed in the configuration. - pub fn local_peer_id(&self) -> &PeerId { - self.kbuckets.my_id() - } - - /// Finds the known nodes closest to `id`, ordered by distance. - pub fn known_closest_peers(&self, id: &PeerId) -> impl Iterator { - self.kbuckets.find_closest_with_self(id) - } - - /// Starts a query for an iterative `FIND_NODE` request. - pub fn find_node<'a, F, Fut>(&self, searched_key: PeerId, access: F) - -> impl Stream>, Error = IoError> + 'a - where F: FnMut(&PeerId) -> Fut + Send + 'a, - Fut: IntoFuture + 'a, - Fut::Future: Send, - { - query(access, &self.kbuckets, searched_key, self.parallelism as usize, - 20, self.request_timeout) // TODO: arbitrary const - } -} - -// Refreshes a specific bucket by performing an iterative `FIND_NODE` on a random ID of this -// bucket. -// -// Returns a dummy no-op future if `bucket_num` is out of range. -fn refresh<'a, F, Fut>(bucket_num: usize, access: F, kbuckets: &KBucketsTable, - parallelism: usize, request_timeout: Duration) - -> impl Stream, Error = IoError> + 'a -where F: FnMut(&PeerId) -> Fut + Send + 'a, - Fut: IntoFuture + 'a, - Fut::Future: Send, -{ - let peer_id = match gen_random_id(kbuckets.my_id(), bucket_num) { - Ok(p) => p, - Err(()) => { - let stream = stream::once(Ok(KadQueryEvent::Finished(()))); - return Box::new(stream) as Box + Send>; - }, - }; - - let stream = query(access, kbuckets, peer_id, parallelism, 20, request_timeout) // TODO: 20 is arbitrary - .map(|event| { - match event { - KadQueryEvent::PeersReported(peers) => KadQueryEvent::PeersReported(peers), - KadQueryEvent::Finished(_) => KadQueryEvent::Finished(()), - } - }); - Box::new(stream) as Box + Send> -} - -// Generates a random `PeerId` that belongs to the given bucket. -// -// Returns an error if `bucket_num` is out of range. -fn gen_random_id(my_id: &PeerId, bucket_num: usize) -> Result { - let my_id_len = my_id.as_bytes().len(); - - // TODO: this 2 is magic here; it is the length of the hash of the multihash - let bits_diff = bucket_num + 1; - if bits_diff > 8 * (my_id_len - 2) { - return Err(()); - } - - let mut random_id = [0; 64]; - for byte in 0..my_id_len { - match byte.cmp(&(my_id_len - bits_diff / 8 - 1)) { - Ordering::Less => { - random_id[byte] = my_id.as_bytes()[byte]; - } - Ordering::Equal => { - let mask: u8 = (1 << (bits_diff % 8)) - 1; - random_id[byte] = (my_id.as_bytes()[byte] & !mask) | (rand::random::() & mask); - } - Ordering::Greater => { - random_id[byte] = rand::random(); - } - } - } - - let peer_id = PeerId::from_bytes(random_id[..my_id_len].to_owned()) - .expect("randomly-generated peer ID should always be valid"); - Ok(peer_id) -} - -// Generic query-performing function. -fn query<'a, F, Fut>( - access: F, - kbuckets: &KBucketsTable, - searched_key: PeerId, - parallelism: usize, - num_results: usize, - request_timeout: Duration, -) -> impl Stream>, Error = IoError> + 'a -where F: FnMut(&PeerId) -> Fut + 'a, - Fut: IntoFuture + 'a, - Fut::Future: Send, -{ - debug!("Start query for {:?}; num results = {}", searched_key, num_results); - - // State of the current iterative process. - struct State<'a, F> { - // At which stage we are. - stage: Stage, - // The `access` parameter. - access: F, - // Final output of the iteration. - result: Vec, - // For each open connection, a future with the response of the remote. - // Note that don't use a `SmallVec` here because `select_all` produces a `Vec`. - current_attempts_fut: Vec, Error = IoError> + Send + 'a>>, - // For each open connection, the peer ID that we are connected to. - // Must always have the same length as `current_attempts_fut`. - current_attempts_addrs: SmallVec<[PeerId; 32]>, - // Nodes that need to be attempted. - pending_nodes: Vec, - // Peers that we tried to contact but failed. - failed_to_contact: FnvHashSet, - } - - // General stage of the state. - #[derive(Copy, Clone, PartialEq, Eq)] - enum Stage { - // We are still in the first step of the algorithm where we try to find the closest node. - FirstStep, - // We are contacting the k closest nodes in order to fill the list with enough results. - SecondStep, - // The results are complete, and the next stream iteration will produce the outcome. - FinishingNextIter, - // We are finished and the stream shouldn't return anything anymore. - Finished, - } - - let initial_state = State { - stage: Stage::FirstStep, - access: access, - result: Vec::with_capacity(num_results), - current_attempts_fut: Vec::new(), - current_attempts_addrs: SmallVec::new(), - pending_nodes: kbuckets.find_closest(&searched_key).collect(), - failed_to_contact: Default::default(), - }; - - // Start of the iterative process. - let stream = stream::unfold(initial_state, move |mut state| -> Option<_> { - match state.stage { - Stage::FinishingNextIter => { - let result = mem::replace(&mut state.result, Vec::new()); - debug!("Query finished with {} results", result.len()); - state.stage = Stage::Finished; - let future = future::ok((Some(KadQueryEvent::Finished(result)), state)); - return Some(future::Either::A(future)); - }, - Stage::Finished => { - return None; - }, - _ => () - }; - - let searched_key = searched_key.clone(); - - // Find out which nodes to contact at this iteration. - let to_contact = { - let wanted_len = if state.stage == Stage::FirstStep { - parallelism.saturating_sub(state.current_attempts_fut.len()) - } else { - num_results.saturating_sub(state.current_attempts_fut.len()) - }; - let mut to_contact = SmallVec::<[_; 16]>::new(); - while to_contact.len() < wanted_len && !state.pending_nodes.is_empty() { - // Move the first element of `pending_nodes` to `to_contact`, but ignore nodes that - // are already part of the results or of a current attempt or if we failed to - // contact it before. - let peer = state.pending_nodes.remove(0); - if state.result.iter().any(|p| p == &peer) { - continue; - } - if state.current_attempts_addrs.iter().any(|p| p == &peer) { - continue; - } - if state.failed_to_contact.iter().any(|p| p == &peer) { - continue; - } - to_contact.push(peer); - } - to_contact - }; - - debug!("New query round; {} queries in progress; contacting {} new peers", - state.current_attempts_fut.len(), - to_contact.len()); - - // For each node in `to_contact`, start an RPC query and a corresponding entry in the two - // `state.current_attempts_*` fields. - for peer in to_contact { - let searched_key2 = searched_key.clone(); - let current_attempt = (state.access)(&peer) - .into_future() - .and_then(move |controller| { - controller.find_node(&searched_key2) - }); - let with_deadline = Timeout::new(current_attempt, request_timeout) - .map_err(|err| { - if let Some(err) = err.into_inner() { - err - } else { - IoError::new(IoErrorKind::ConnectionAborted, "kademlia request timeout") - } - }); - state.current_attempts_addrs.push(peer.clone()); - state - .current_attempts_fut - .push(Box::new(with_deadline) as Box<_>); - } - debug_assert_eq!( - state.current_attempts_addrs.len(), - state.current_attempts_fut.len() - ); - - // Extract `current_attempts_fut` so that we can pass it to `select_all`. We will push the - // values back when inside the loop. - let current_attempts_fut = mem::replace(&mut state.current_attempts_fut, Vec::new()); - if current_attempts_fut.is_empty() { - // If `current_attempts_fut` is empty, then `select_all` would panic. It happens - // when we have no additional node to query. - debug!("Finishing query early because no additional node available"); - state.stage = Stage::FinishingNextIter; - let future = future::ok((None, state)); - return Some(future::Either::A(future)); - } - - // This is the future that continues or breaks the `loop_fn`. - let future = future::select_all(current_attempts_fut.into_iter()).then(move |result| { - let (message, trigger_idx, other_current_attempts) = match result { - Err((err, trigger_idx, other_current_attempts)) => { - (Err(err), trigger_idx, other_current_attempts) - } - Ok((message, trigger_idx, other_current_attempts)) => { - (Ok(message), trigger_idx, other_current_attempts) - } - }; - - // Putting back the extracted elements in `state`. - let remote_id = state.current_attempts_addrs.remove(trigger_idx); - debug_assert!(state.current_attempts_fut.is_empty()); - state.current_attempts_fut = other_current_attempts; - - // `message` contains the reason why the current future was woken up. - let closer_peers = match message { - Ok(msg) => msg, - Err(err) => { - trace!("RPC query failed for {:?}: {:?}", remote_id, err); - state.failed_to_contact.insert(remote_id); - return future::ok((None, state)); - } - }; - - // Inserting the node we received a response from into `state.result`. - // The code is non-trivial because `state.result` is ordered by distance and is limited - // by `num_results` elements. - if let Some(insert_pos) = state.result.iter().position(|e| { - e.distance_with(&searched_key) >= remote_id.distance_with(&searched_key) - }) { - if state.result[insert_pos] != remote_id { - if state.result.len() >= num_results { - state.result.pop(); - } - state.result.insert(insert_pos, remote_id); - } - } else if state.result.len() < num_results { - state.result.push(remote_id); - } - - // The loop below will set this variable to `true` if we find a new element to put at - // the top of the result. This would mean that we have to continue looping. - let mut local_nearest_node_updated = false; - - // Update `state` with the actual content of the message. - let mut peers_reported = Vec::with_capacity(closer_peers.len()); - for mut peer in closer_peers { - // Update the peerstore with the information sent by - // the remote. - trace!("Reporting multiaddresses for {:?}: {:?}", peer.node_id, peer.multiaddrs); - peers_reported.push(peer.clone()); - - if peer.node_id.distance_with(&searched_key) - <= state.result[0].distance_with(&searched_key) - { - local_nearest_node_updated = true; - } - - if state.result.iter().any(|ma| ma == &peer.node_id) { - continue; - } - - // Insert the node into `pending_nodes` at the right position, or do not - // insert it if it is already in there. - if let Some(insert_pos) = state.pending_nodes.iter().position(|e| { - e.distance_with(&searched_key) >= peer.node_id.distance_with(&searched_key) - }) { - if state.pending_nodes[insert_pos] != peer.node_id { - state.pending_nodes.insert(insert_pos, peer.node_id.clone()); - } - } else { - state.pending_nodes.push(peer.node_id.clone()); - } - } - - if state.result.len() >= num_results - || (state.stage != Stage::FirstStep && state.current_attempts_fut.is_empty()) - { - state.stage = Stage::FinishingNextIter; - - } else { - if !local_nearest_node_updated { - trace!("Loop didn't update closer node; jumping to step 2"); - state.stage = Stage::SecondStep; - } - } - - future::ok((Some(KadQueryEvent::PeersReported(peers_reported)), state)) - }); - - Some(future::Either::B(future)) - }).filter_map(|val| val); - - // Boxing the stream is not necessary, but we do it in order to improve compilation time. - Box::new(stream) as Box<_> -} diff --git a/protocols/kad/src/kad_server.rs b/protocols/kad/src/kad_server.rs deleted file mode 100644 index 0a22c843f7a..00000000000 --- a/protocols/kad/src/kad_server.rs +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Contains a `ConnectionUpgrade` that makes it possible to send requests and receive responses -//! from nodes after the upgrade. -//! -//! # Usage -//! -//! - Create a `KadConnecConfig` object. This struct implements `ConnectionUpgrade`. -//! -//! - Update a connection through that `KadConnecConfig`. The output yields you a -//! `KadConnecController` and a stream that must be driven to completion. The controller -//! allows you to perform queries and receive responses. The stream produces incoming requests -//! from the remote. -//! -//! This `KadConnecController` is usually extracted and stored in some sort of hash map in an -//! `Arc` in order to be available whenever we need to request something from a node. - -use bytes::Bytes; -use futures::sync::{mpsc, oneshot}; -use futures::{future, Future, Sink, stream, Stream}; -use libp2p_core::{PeerId, upgrade::{InboundUpgrade, UpgradeInfo}}; -use log::{debug, warn}; -use multihash::Multihash; -use protocol::{self, KadMsg, KademliaProtocolConfig, KadPeer}; -use std::collections::VecDeque; -use std::io::{Error as IoError, ErrorKind as IoErrorKind}; -use std::iter; -use tokio_io::{AsyncRead, AsyncWrite}; - -/// Configuration for a Kademlia server. -/// -/// Implements `ConnectionUpgrade`. On a successful upgrade, produces a `KadConnecController` -/// and a `Future`. The controller lets you send queries to the remote and receive answers, while -/// the `Future` must be driven to completion in order for things to work. -#[derive(Debug, Clone)] -pub struct KadConnecConfig { - raw_proto: KademliaProtocolConfig, -} - -impl KadConnecConfig { - /// Builds a configuration object for an upcoming Kademlia server. - #[inline] - pub fn new() -> Self { - KadConnecConfig { - raw_proto: KademliaProtocolConfig, - } - } -} - -impl UpgradeInfo for KadConnecConfig { - type NamesIter = iter::Once<(Bytes, Self::UpgradeId)>; - type UpgradeId = (); - - #[inline] - fn protocol_names(&self) -> Self::NamesIter { - self.raw_proto.protocol_names() - } -} - -impl InboundUpgrade for KadConnecConfig -where - C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/ -{ - type Output = ( - KadConnecController, - Box + Send>, - ); - type Error = IoError; - type Future = future::Map<>::Future, fn(>::Output) -> Self::Output>; - - #[inline] - fn upgrade_inbound(self, incoming: C, id: Self::UpgradeId) -> Self::Future { - self.raw_proto - .upgrade_inbound(incoming, id) - .map(build_from_sink_stream) - } -} - -/// Allows sending Kademlia requests and receiving responses. -#[derive(Debug, Clone)] -pub struct KadConnecController { - // In order to send a request, we use this sender to send a tuple. The first element of the - // tuple is the message to send to the remote, and the second element is what is used to - // receive the response. If the query doesn't expect a response (e.g. `PUT_VALUE`), then the - // one-shot sender will be dropped without being used. - inner: mpsc::UnboundedSender<(KadMsg, oneshot::Sender)>, -} - -impl KadConnecController { - /// Sends a `FIND_NODE` query to the node and provides a future that will contain the response. - // TODO: future item could be `impl Iterator` instead - pub fn find_node( - &self, - searched_key: &PeerId, - ) -> impl Future, Error = IoError> { - let message = protocol::KadMsg::FindNodeReq { - key: searched_key.clone().into(), - }; - - let (tx, rx) = oneshot::channel(); - - match self.inner.unbounded_send((message, tx)) { - Ok(()) => (), - Err(_) => { - let fut = future::err(IoError::new( - IoErrorKind::ConnectionAborted, - "connection to remote has aborted", - )); - - return future::Either::B(fut); - } - }; - - let future = rx.map_err(|_| { - IoError::new( - IoErrorKind::ConnectionAborted, - "connection to remote has aborted", - ) - }).and_then(|msg| match msg { - KadMsg::FindNodeRes { closer_peers, .. } => Ok(closer_peers), - _ => Err(IoError::new( - IoErrorKind::InvalidData, - "invalid response type received from the remote", - )), - }); - - future::Either::A(future) - } - - /// Sends a `GET_PROVIDERS` query to the node and provides a future that will contain the response. - // TODO: future item could be `impl Iterator` instead - pub fn get_providers( - &self, - searched_key: &Multihash, - ) -> impl Future, Vec), Error = IoError> { - let message = protocol::KadMsg::GetProvidersReq { - key: searched_key.clone(), - }; - - let (tx, rx) = oneshot::channel(); - - match self.inner.unbounded_send((message, tx)) { - Ok(()) => (), - Err(_) => { - let fut = future::err(IoError::new( - IoErrorKind::ConnectionAborted, - "connection to remote has aborted", - )); - - return future::Either::B(fut); - } - }; - - let future = rx.map_err(|_| { - IoError::new( - IoErrorKind::ConnectionAborted, - "connection to remote has aborted", - ) - }).and_then(|msg| match msg { - KadMsg::GetProvidersRes { closer_peers, provider_peers } => Ok((closer_peers, provider_peers)), - _ => Err(IoError::new( - IoErrorKind::InvalidData, - "invalid response type received from the remote", - )), - }); - - future::Either::A(future) - } - - /// Sends an `ADD_PROVIDER` message to the node. - pub fn add_provider(&self, key: Multihash, provider_peer: KadPeer) -> Result<(), IoError> { - // Dummy channel, as the `tx` is going to be dropped anyway. - let (tx, _rx) = oneshot::channel(); - let message = protocol::KadMsg::AddProvider { - key, - provider_peer, - }; - match self.inner.unbounded_send((message, tx)) { - Ok(()) => Ok(()), - Err(_) => Err(IoError::new( - IoErrorKind::ConnectionAborted, - "connection to remote has aborted", - )), - } - } - - /// Sends a `PING` query to the node. Because of the way the protocol is designed, there is - /// no way to differentiate between a ping and a pong. Therefore this function doesn't return a - /// future, and the only way to be notified of the result is through the stream. - pub fn ping(&self) -> Result<(), IoError> { - // Dummy channel, as the `tx` is going to be dropped anyway. - let (tx, _rx) = oneshot::channel(); - match self.inner.unbounded_send((protocol::KadMsg::Ping, tx)) { - Ok(()) => Ok(()), - Err(_) => Err(IoError::new( - IoErrorKind::ConnectionAborted, - "connection to remote has aborted", - )), - } - } -} - -/// Request received from the remote. -pub enum KadIncomingRequest { - /// Find the nodes closest to `searched`. - FindNode { - /// The value being searched. - searched: PeerId, - /// Object to use to respond to the request. - responder: KadFindNodeRespond, - }, - - /// Find the nodes closest to `searched` and return the known providers for `searched`. - GetProviders { - /// The value being searched. - searched: Multihash, - /// Object to use to respond to the request. - responder: KadGetProvidersRespond, - }, - - /// Registers a provider for the given key. - /// - /// The local node is supposed to remember this and return the provider on a `GetProviders` - /// request for the given key. - AddProvider { - /// The key of the provider. - key: Multihash, - /// The provider to register. - provider_peer: KadPeer, - }, - - /// Received either a ping or a pong. - PingPong, - - // TODO: PutValue and FindValue -} - -/// Object used to respond to `FindNode` queries from remotes. -pub struct KadFindNodeRespond { - inner: oneshot::Sender, -} - -impl KadFindNodeRespond { - /// Respond to the `FindNode` request. - pub fn respond(self, peers: I) - where I: IntoIterator - { - let _ = self.inner.send(KadMsg::FindNodeRes { - closer_peers: peers.into_iter().collect() - }); - } -} - -/// Object used to respond to `GetProviders` queries from remotes. -pub struct KadGetProvidersRespond { - inner: oneshot::Sender, -} - -impl KadGetProvidersRespond { - /// Respond to the `GetProviders` request. - pub fn respond(self, closest_peers: Ic, providers: Ip) - where Ic: IntoIterator, - Ip: IntoIterator, - { - let _ = self.inner.send(KadMsg::GetProvidersRes { - closer_peers: closest_peers.into_iter().collect(), - provider_peers: providers.into_iter().collect(), - }); - } -} - -// Builds a controller and stream from a stream/sink of raw messages. -fn build_from_sink_stream<'a, S>(connec: S) -> (KadConnecController, Box + Send + 'a>) -where S: Sink + Stream + Send + 'a -{ - let (tx, rx) = mpsc::unbounded(); - let future = kademlia_handler(connec, rx); - let controller = KadConnecController { inner: tx }; - (controller, future) -} - -// Handles a newly-opened Kademlia stream with a remote peer. -// -// Takes a `Stream` and `Sink` of Kademlia messages representing the connection to the client, -// plus a `Receiver` that will receive messages to transmit to that connection. -// -// Returns a `Stream` that must be resolved in order for progress to work. The `Stream` will -// produce objects that represent the requests sent by the remote. These requests must be answered -// immediately before the stream continues to produce items. -fn kademlia_handler<'a, S>( - kad_bistream: S, - rq_rx: mpsc::UnboundedReceiver<(KadMsg, oneshot::Sender)>, -) -> Box + Send + 'a> -where - S: Stream + Sink + Send + 'a, -{ - let (kad_sink, kad_stream) = kad_bistream.split(); - - // This is a stream of futures containing local responses. - // Every time we receive a request from the remote, we create a `oneshot::channel()` and send - // the receiving end to `responders_tx`. - // This way, if a future is available on `responders_rx`, we block until it produces the - // response. - let (responders_tx, responders_rx) = mpsc::unbounded(); - - // We combine all the streams into one so that the loop wakes up whenever any generates - // something. - enum EventSource { - Remote(KadMsg), - LocalRequest(KadMsg, oneshot::Sender), - LocalResponse(oneshot::Receiver), - Finished, - } - - let events = { - let responders = responders_rx - .map(|m| EventSource::LocalResponse(m)) - .map_err(|_| unreachable!()); - let rq_rx = rq_rx - .map(|(m, o)| EventSource::LocalRequest(m, o)) - .map_err(|_| unreachable!()); - let kad_stream = kad_stream - .map(|m| EventSource::Remote(m)) - .chain(future::ok(EventSource::Finished).into_stream()); - responders.select(rq_rx).select(kad_stream) - }; - - let stream = stream::unfold((events, kad_sink, responders_tx, VecDeque::new(), 0u32, false), - move |(events, kad_sink, responders_tx, mut send_back_queue, expected_pongs, finished)| { - if finished { - return None; - } - - Some(events - .into_future() - .map_err(|(err, _)| err) - .and_then(move |(message, events)| -> Box + Send> { - match message { - Some(EventSource::Finished) | None => { - let future = future::ok({ - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, true); - (None, state) - }); - Box::new(future) - }, - Some(EventSource::LocalResponse(message)) => { - let future = message - .map_err(|err| { - // The user destroyed the responder without responding. - warn!("Kad responder object destroyed without responding"); - // TODO: what to do here? we have to close the connection - IoError::new(IoErrorKind::Other, err) - }) - .and_then(move |message| { - kad_sink - .send(message) - .map(move |kad_sink| { - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - (None, state) - }) - }); - Box::new(future) - }, - Some(EventSource::LocalRequest(message @ KadMsg::PutValue { .. }, _)) | - Some(EventSource::LocalRequest(message @ KadMsg::AddProvider { .. }, _)) => { - // A `PutValue` or `AddProvider` request. Contrary to other types of - // messages, these ones don't expect any answer and therefore we ignore - // the sender. - let future = kad_sink - .send(message) - .map(move |kad_sink| { - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - (None, state) - }); - Box::new(future) as Box<_> - } - Some(EventSource::LocalRequest(message @ KadMsg::Ping { .. }, _)) => { - // A local `Ping` request. - let expected_pongs = expected_pongs.checked_add(1) - .expect("overflow in number of simultaneous pings"); - let future = kad_sink - .send(message) - .map(move |kad_sink| { - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - (None, state) - }); - Box::new(future) as Box<_> - } - Some(EventSource::LocalRequest(message, send_back)) => { - // Any local request other than `PutValue` or `Ping`. - send_back_queue.push_back(send_back); - let future = kad_sink - .send(message) - .map(move |kad_sink| { - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - (None, state) - }); - Box::new(future) as Box<_> - } - Some(EventSource::Remote(KadMsg::Ping)) => { - // The way the protocol was designed, there is no way to differentiate - // between a ping and a pong. - if let Some(expected_pongs) = expected_pongs.checked_sub(1) { - // Maybe we received a PONG, or maybe we received a PONG, no way - // to tell. If it was a PING and we expected a PONG, then the - // remote will see its PING answered only when it PONGs us. - let future = future::ok({ - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - let rq = KadIncomingRequest::PingPong; - (Some(rq), state) - }); - Box::new(future) as Box<_> - } else { - let future = kad_sink - .send(KadMsg::Ping) - .map(move |kad_sink| { - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - let rq = KadIncomingRequest::PingPong; - (Some(rq), state) - }); - Box::new(future) as Box<_> - } - } - Some(EventSource::Remote(message @ KadMsg::FindNodeRes { .. })) - | Some(EventSource::Remote(message @ KadMsg::GetValueRes { .. })) - | Some(EventSource::Remote(message @ KadMsg::GetProvidersRes { .. })) => { - // `FindNodeRes`, `GetValueRes` or `GetProvidersRes` received on the socket. - // Send it back through `send_back_queue`. - if let Some(send_back) = send_back_queue.pop_front() { - let _ = send_back.send(message); - let future = future::ok({ - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - (None, state) - }); - Box::new(future) - } else { - debug!("Remote sent a Kad response but we didn't request anything"); - let future = future::err(IoErrorKind::InvalidData.into()); - Box::new(future) - } - } - Some(EventSource::Remote(KadMsg::FindNodeReq { key })) => { - let (tx, rx) = oneshot::channel(); - let _ = responders_tx.unbounded_send(rx); - let future = future::ok({ - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - let rq = KadIncomingRequest::FindNode { - searched: key, - responder: KadFindNodeRespond { - inner: tx - } - }; - (Some(rq), state) - }); - - Box::new(future) - } - Some(EventSource::Remote(KadMsg::GetProvidersReq { key })) => { - let (tx, rx) = oneshot::channel(); - let _ = responders_tx.unbounded_send(rx); - let future = future::ok({ - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - let rq = KadIncomingRequest::GetProviders { - searched: key, - responder: KadGetProvidersRespond { - inner: tx - } - }; - (Some(rq), state) - }); - - Box::new(future) - } - Some(EventSource::Remote(KadMsg::AddProvider { key, provider_peer })) => { - let future = future::ok({ - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - let rq = KadIncomingRequest::AddProvider { key, provider_peer }; - (Some(rq), state) - }); - Box::new(future) as Box<_> - } - Some(EventSource::Remote(KadMsg::GetValueReq { .. })) => { - warn!("GET_VALUE requests are not implemented yet"); - let future = future::err(IoError::new(IoErrorKind::Other, - "GET_VALUE requests are not implemented yet")); - return Box::new(future); - } - Some(EventSource::Remote(KadMsg::PutValue { .. })) => { - warn!("PUT_VALUE requests are not implemented yet"); - let state = (events, kad_sink, responders_tx, send_back_queue, expected_pongs, finished); - let future = future::ok((None, state)); - return Box::new(future); - } - } - })) - }).filter_map(|val| val); - - Box::new(stream) as Box + Send> -} - -#[cfg(test)] -mod tests { - use std::io::Error as IoError; - use std::iter; - use futures::{Future, Poll, Sink, StartSend, Stream}; - use futures::sync::mpsc; - use kad_server::{self, KadIncomingRequest, KadConnecController}; - use libp2p_core::PeerId; - use protocol::{KadConnectionType, KadPeer}; - - // This struct merges a stream and a sink and is quite useful for tests. - struct Wrapper(St, Si); - impl Stream for Wrapper - where - St: Stream, - { - type Item = St::Item; - type Error = St::Error; - fn poll(&mut self) -> Poll, Self::Error> { - self.0.poll() - } - } - impl Sink for Wrapper - where - Si: Sink, - { - type SinkItem = Si::SinkItem; - type SinkError = Si::SinkError; - fn start_send( - &mut self, - item: Self::SinkItem, - ) -> StartSend { - self.1.start_send(item) - } - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.1.poll_complete() - } - fn close(&mut self) -> Poll<(), Self::SinkError> { - self.1.close() - } - } - - fn build_test() -> (KadConnecController, impl Stream, KadConnecController, impl Stream) { - let (a_to_b, b_from_a) = mpsc::unbounded(); - let (b_to_a, a_from_b) = mpsc::unbounded(); - - let sink_stream_a = Wrapper(a_from_b, a_to_b) - .map_err(|_| panic!()).sink_map_err(|_| panic!()); - let sink_stream_b = Wrapper(b_from_a, b_to_a) - .map_err(|_| panic!()).sink_map_err(|_| panic!()); - - let (controller_a, stream_events_a) = kad_server::build_from_sink_stream(sink_stream_a); - let (controller_b, stream_events_b) = kad_server::build_from_sink_stream(sink_stream_b); - (controller_a, stream_events_a, controller_b, stream_events_b) - } - - #[test] - fn ping_response() { - let (controller_a, stream_events_a, _controller_b, stream_events_b) = build_test(); - - controller_a.ping().unwrap(); - - let streams = stream_events_a.map(|ev| (ev, "a")) - .select(stream_events_b.map(|ev| (ev, "b"))); - match streams.into_future().map_err(|(err, _)| err).wait().unwrap() { - (Some((KadIncomingRequest::PingPong, "b")), _) => {}, - _ => panic!() - } - } - - #[test] - fn find_node_response() { - let (controller_a, stream_events_a, _controller_b, stream_events_b) = build_test(); - - let random_peer_id = PeerId::random(); - - let find_node_fut = controller_a.find_node(&random_peer_id); - - let example_response = KadPeer { - node_id: PeerId::random(), - multiaddrs: Vec::new(), - connection_ty: KadConnectionType::Connected, - }; - - let streams = stream_events_a.map(|ev| (ev, "a")) - .select(stream_events_b.map(|ev| (ev, "b"))); - - let streams = match streams.into_future().map_err(|(err, _)| err).wait().unwrap() { - (Some((KadIncomingRequest::FindNode { searched, responder }, "b")), streams) => { - assert_eq!(searched, random_peer_id); - responder.respond(iter::once(example_response.clone())); - streams - }, - _ => panic!() - }; - - let resp = streams.into_future().map_err(|(err, _)| err).map(|_| unreachable!()) - .select(find_node_fut) - .map_err(|_| -> IoError { panic!() }); - assert_eq!(resp.wait().unwrap().0, vec![example_response]); - } -} diff --git a/protocols/kad/src/kbucket.rs b/protocols/kad/src/kbucket.rs index 790f463e919..59a1119ae40 100644 --- a/protocols/kad/src/kbucket.rs +++ b/protocols/kad/src/kbucket.rs @@ -29,7 +29,7 @@ use arrayvec::ArrayVec; use bigint::U512; -use libp2p_core::PeerId; +use multihash::Multihash; use parking_lot::{Mutex, MutexGuard}; use std::mem; use std::slice::Iter as SliceIter; @@ -57,7 +57,8 @@ where fn clone(&self) -> Self { KBucketsTable { my_id: self.my_id.clone(), - tables: self.tables + tables: self + .tables .iter() .map(|t| t.lock().clone()) .map(Mutex::new) @@ -124,7 +125,7 @@ pub trait KBucketsPeerId: Eq + Clone { fn leading_zeros(Self::Distance) -> u32; } -impl KBucketsPeerId for PeerId { +impl KBucketsPeerId for Multihash { type Distance = U512; #[inline] @@ -201,7 +202,7 @@ where let mut table = table.lock(); table.flush(self.ping_timeout); if table.last_update.elapsed() > self.ping_timeout { - continue // ignore bucket with expired nodes + continue; // ignore bucket with expired nodes } for node in table.nodes.iter() { out.push(node.id.clone()); @@ -357,7 +358,7 @@ mod tests { extern crate rand; use self::rand::random; use kbucket::{KBucketsTable, UpdateOutcome, MAX_NODES_PER_BUCKET}; - use libp2p_core::PeerId; + use multihash::Multihash; use std::thread; use std::time::Duration; @@ -367,14 +368,20 @@ mod tests { let mut bytes = vec![random(); 34]; bytes[0] = 18; bytes[1] = 32; - PeerId::from_bytes(bytes.clone()).expect(&format!("creating `my_id` PeerId from bytes {:#?} failed", bytes)) + Multihash::from_bytes(bytes.clone()).expect(&format!( + "creating `my_id` Multihash from bytes {:#?} failed", + bytes + )) }; let other_id = { let mut bytes = vec![random(); 34]; bytes[0] = 18; bytes[1] = 32; - PeerId::from_bytes(bytes.clone()).expect(&format!("creating `other_id` PeerId from bytes {:#?} failed", bytes)) + Multihash::from_bytes(bytes.clone()).expect(&format!( + "creating `other_id` Multihash from bytes {:#?} failed", + bytes + )) }; let table = KBucketsTable::new(my_id, Duration::from_secs(5)); @@ -391,13 +398,13 @@ mod tests { let mut bytes = vec![random(); 34]; bytes[0] = 18; bytes[1] = 32; - PeerId::from_bytes(bytes).unwrap() + Multihash::from_bytes(bytes).unwrap() }; let table = KBucketsTable::new(my_id.clone(), Duration::from_secs(5)); match table.update(my_id, ()) { UpdateOutcome::FailSelfUpdate => (), - _ => panic!() + _ => panic!(), } } @@ -407,7 +414,7 @@ mod tests { let mut bytes = vec![random(); 34]; bytes[0] = 18; bytes[1] = 32; - PeerId::from_bytes(bytes).unwrap() + Multihash::from_bytes(bytes).unwrap() }; // Generate some other IDs varying by just one bit. @@ -416,7 +423,7 @@ mod tests { let bit_num = random::() % 256; let mut id = my_id.as_bytes().to_vec().clone(); id[33 - (bit_num / 8)] ^= 1 << (bit_num % 8); - (PeerId::from_bytes(id).unwrap(), bit_num) + (Multihash::from_bytes(id).unwrap(), bit_num) }) .collect::>(); @@ -445,7 +452,7 @@ mod tests { let mut bytes = vec![random(); 34]; bytes[0] = 18; bytes[1] = 32; - PeerId::from_bytes(bytes).unwrap() + Multihash::from_bytes(bytes).unwrap() }; assert!(MAX_NODES_PER_BUCKET <= 251); // Test doesn't work otherwise. @@ -454,7 +461,7 @@ mod tests { let mut id = my_id.clone().into_bytes(); id[2] ^= 0x80; // Flip the first bit so that we get in the most distant bucket. id[33] = id[33].wrapping_add(n as u8); - PeerId::from_bytes(id).unwrap() + Multihash::from_bytes(id).unwrap() }) .collect::>(); diff --git a/protocols/kad/src/lib.rs b/protocols/kad/src/lib.rs index 49b1281ada4..3af65768b3a 100644 --- a/protocols/kad/src/lib.rs +++ b/protocols/kad/src/lib.rs @@ -62,10 +62,11 @@ extern crate bs58; extern crate bytes; extern crate datastore; extern crate fnv; +#[cfg_attr(test, macro_use)] extern crate futures; +extern crate libp2p_core; extern crate libp2p_identify; extern crate libp2p_ping; -extern crate libp2p_core; extern crate log; extern crate multiaddr; extern crate multihash; @@ -77,13 +78,21 @@ extern crate tokio_codec; extern crate tokio_io; extern crate tokio_timer; extern crate unsigned_varint; +extern crate void; + +#[cfg(test)] +extern crate tokio; + +pub use self::behaviour::{Kademlia, KademliaOut}; +pub use self::kbucket::KBucketsPeerId; +pub use self::protocol::KadConnectionType; +pub use self::topology::KademliaTopology; -pub use self::high_level::{KadSystemConfig, KadSystem, KadQueryEvent}; -pub use self::kad_server::{KadConnecController, KadConnecConfig, KadIncomingRequest, KadFindNodeRespond}; -pub use self::protocol::{KadConnectionType, KadPeer}; +pub mod handler; +pub mod protocol; -mod high_level; -mod kad_server; +mod behaviour; mod kbucket; mod protobuf_structs; -mod protocol; +mod query; +mod topology; diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index e682af83ed4..f2787d98541 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -18,19 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Provides the `KadMsg` enum of all the possible messages transmitted with the Kademlia protocol, -//! and the `KademliaProtocolConfig` connection upgrade whose output is a -//! `Stream + Sink`. +//! Provides the `KadRequestMsg` and `KadResponseMsg` enums of all the possible messages +//! transmitted with the Kademlia protocol, and the `KademliaProtocolConfig` connection upgrade. +//! +//! The upgrade's output a `Sink + Stream` of messages. //! //! The `Stream` component is used to poll the underlying transport, and the `Sink` component is //! used to send messages. use bytes::{Bytes, BytesMut}; -use futures::{future, sink, Sink, stream, Stream}; -use libp2p_core::{ - Multiaddr, PeerId, - upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo} -}; +use futures::{future, sink, stream, Sink, Stream}; +use libp2p_core::{InboundUpgrade, Multiaddr, OutboundUpgrade, PeerId, UpgradeInfo}; use multihash::Multihash; use protobuf::{self, Message}; use protobuf_structs; @@ -127,13 +125,13 @@ impl Into for KadPeer { } /// Configuration for a Kademlia connection upgrade. When applied to a connection, turns this -/// connection into a `Stream + Sink` whose items are of type `KadMsg`. +/// connection into a `Stream + Sink` whose items are of type `KadRequestMsg` and `KadResponseMsg`. #[derive(Debug, Default, Copy, Clone)] pub struct KademliaProtocolConfig; impl UpgradeInfo for KademliaProtocolConfig { + type NamesIter = iter::Once<(Bytes, ())>; type UpgradeId = (); - type NamesIter = iter::Once<(Bytes, Self::UpgradeId)>; #[inline] fn protocol_names(&self) -> Self::NamesIter { @@ -143,56 +141,85 @@ impl UpgradeInfo for KademliaProtocolConfig { impl InboundUpgrade for KademliaProtocolConfig where - C: AsyncRead + AsyncWrite + 'static, // TODO: 'static :-/ + C: AsyncRead + AsyncWrite, { - type Output = KadStreamSink; + type Output = KadInStreamSink; + type Future = future::FutureResult; type Error = IoError; - type Future = future::FutureResult; #[inline] - fn upgrade_inbound(self, incoming: C, _: Self::UpgradeId) -> Self::Future { - future::ok(kademlia_protocol(incoming)) + fn upgrade_inbound(self, incoming: C, _: ()) -> Self::Future { + future::ok( + Framed::new(incoming, codec::UviBytes::default()) + .from_err::() + .with::<_, fn(_) -> _, _>(|response| -> Result<_, IoError> { + let proto_struct = resp_msg_to_proto(response); + Ok(proto_struct.write_to_bytes().unwrap()) // TODO: error? + }) + .and_then:: _, _>(|bytes: BytesMut| { + let request = protobuf::parse_from_bytes(&bytes)?; + proto_to_req_msg(request) + }), + ) } } impl OutboundUpgrade for KademliaProtocolConfig where - C: AsyncRead + AsyncWrite + 'static, // TODO: 'static :-/ + C: AsyncRead + AsyncWrite, { - type Output = KadStreamSink; + type Output = KadOutStreamSink; + type Future = future::FutureResult; type Error = IoError; - type Future = future::FutureResult; #[inline] - fn upgrade_outbound(self, incoming: C, _: Self::UpgradeId) -> Self::Future { - future::ok(kademlia_protocol(incoming)) + fn upgrade_outbound(self, incoming: C, _: ()) -> Self::Future { + future::ok( + Framed::new(incoming, codec::UviBytes::default()) + .from_err::() + .with::<_, fn(_) -> _, _>(|request| -> Result<_, IoError> { + let proto_struct = req_msg_to_proto(request); + match proto_struct.write_to_bytes() { + Ok(msg) => Ok(msg), + Err(err) => Err(IoError::new(IoErrorKind::Other, err.to_string())), + } + }) + .and_then:: _, _>(|bytes: BytesMut| { + let response = protobuf::parse_from_bytes(&bytes)?; + proto_to_resp_msg(response) + }), + ) } } -type KadStreamSink = stream::AndThen>>, IoError>, KadMsg, fn(KadMsg) -> Result, IoError>, Result, IoError>>, fn(BytesMut) -> Result, Result>; - -// Upgrades a socket to use the Kademlia protocol. -fn kademlia_protocol(socket: S) -> KadStreamSink -where - S: AsyncRead + AsyncWrite, -{ - Framed::new(socket, codec::UviBytes::default()) - .from_err::() - .with::<_, fn(_) -> _, _>(|request| -> Result<_, IoError> { - let proto_struct = msg_to_proto(request); - Ok(proto_struct.write_to_bytes().unwrap()) // TODO: error? - }) - .and_then:: _, _>(|bytes| { - let response = protobuf::parse_from_bytes(&bytes)?; - proto_to_msg(response) - }) -} - -/// Message that we can send to a peer or received from a peer. +/// Sink of responses and stream of requests. +pub type KadInStreamSink = stream::AndThen< + sink::With< + stream::FromErr>>, IoError>, + KadResponseMsg, + fn(KadResponseMsg) -> Result, IoError>, + Result, IoError>, + >, + fn(BytesMut) -> Result, + Result, +>; +/// Sink of requests and stream of responses. +pub type KadOutStreamSink = stream::AndThen< + sink::With< + stream::FromErr>>, IoError>, + KadRequestMsg, + fn(KadRequestMsg) -> Result, IoError>, + Result, IoError>, + >, + fn(BytesMut) -> Result, + Result, +>; + +/// Request that we can send to a peer or that we received from a peer. // TODO: document the rest #[derive(Debug, Clone, PartialEq, Eq)] -pub enum KadMsg { - /// Ping request or response. +pub enum KadRequestMsg { + /// Ping request. Ping, /// Target must save the given record, can be queried later with `GetValueReq`. @@ -203,46 +230,25 @@ pub enum KadMsg { record: (), //record: protobuf_structs::record::Record, // TODO: no }, - GetValueReq { + GetValue { /// Identifier of the record. key: Multihash, }, - GetValueRes { - /// Identifier of the returned record. - key: Multihash, - record: (), //record: Option, // TODO: no - closer_peers: Vec, - }, - /// Request for the list of nodes whose IDs are the closest to `key`. The number of nodes /// returned is not specified, but should be around 20. - FindNodeReq { + FindNode { /// Identifier of the node. key: PeerId, }, - /// Response to a `FindNodeReq`. - FindNodeRes { - /// Results of the request. - closer_peers: Vec, - }, - - /// Same as `FindNodeReq`, but should also return the entries of the local providers list for + /// Same as `FindNode`, but should also return the entries of the local providers list for /// this key. - GetProvidersReq { + GetProviders { /// Identifier being searched. key: Multihash, }, - /// Response to a `FindNodeReq`. - GetProvidersRes { - /// Nodes closest to the key. - closer_peers: Vec, - /// Known providers for this key. - provider_peers: Vec, - }, - /// Indicates that this list of providers is known for this key. AddProvider { /// Key for which we should add providers. @@ -252,39 +258,91 @@ pub enum KadMsg { }, } -// Turns a type-safe kadmelia message into the corresponding row protobuf message. -fn msg_to_proto(kad_msg: KadMsg) -> protobuf_structs::dht::Message { +/// Response that we can send to a peer or that we received from a peer. +// TODO: document the rest +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum KadResponseMsg { + /// Ping response. + Pong, + + /// Response to a `GetValue`. + GetValue { + record: (), //record: Option, // TODO: no + closer_peers: Vec, + }, + + /// Response to a `FindNode`. + FindNode { + /// Results of the request. + closer_peers: Vec, + }, + + /// Response to a `GetProviders`. + GetProviders { + /// Nodes closest to the key. + closer_peers: Vec, + /// Known providers for this key. + provider_peers: Vec, + }, +} + +// Turns a type-safe Kadmelia message into the corresponding raw protobuf message. +fn req_msg_to_proto(kad_msg: KadRequestMsg) -> protobuf_structs::dht::Message { match kad_msg { - KadMsg::Ping => { + KadRequestMsg::Ping => { let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::PING); msg } - KadMsg::PutValue { key, .. } => { + KadRequestMsg::PutValue { key, .. } => { let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::PUT_VALUE); msg.set_key(key.into_bytes()); //msg.set_record(record); // TODO: msg } - KadMsg::GetValueReq { key } => { + KadRequestMsg::GetValue { key } => { let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::GET_VALUE); msg.set_key(key.into_bytes()); msg.set_clusterLevelRaw(10); msg } - KadMsg::GetValueRes { .. } => unimplemented!(), // TODO: - KadMsg::FindNodeReq { key } => { + KadRequestMsg::FindNode { key } => { let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::FIND_NODE); msg.set_key(key.into_bytes()); msg.set_clusterLevelRaw(10); msg } - KadMsg::FindNodeRes { closer_peers } => { - // TODO: if empty, the remote will think it's a request - // TODO: not good, possibly exposed in the API + KadRequestMsg::GetProviders { key } => { + let mut msg = protobuf_structs::dht::Message::new(); + msg.set_field_type(protobuf_structs::dht::Message_MessageType::GET_PROVIDERS); + msg.set_key(key.into_bytes()); + msg.set_clusterLevelRaw(10); + msg + } + KadRequestMsg::AddProvider { key, provider_peer } => { + let mut msg = protobuf_structs::dht::Message::new(); + msg.set_field_type(protobuf_structs::dht::Message_MessageType::ADD_PROVIDER); + msg.set_clusterLevelRaw(10); + msg.set_key(key.into_bytes()); + msg.mut_providerPeers().push(provider_peer.into()); + msg + } + } +} + +// Turns a type-safe Kadmelia message into the corresponding raw protobuf message. +fn resp_msg_to_proto(kad_msg: KadResponseMsg) -> protobuf_structs::dht::Message { + match kad_msg { + KadResponseMsg::Pong => { + let mut msg = protobuf_structs::dht::Message::new(); + msg.set_field_type(protobuf_structs::dht::Message_MessageType::PING); + msg + } + KadResponseMsg::GetValue { .. } => unimplemented!(), // TODO: + KadResponseMsg::FindNode { closer_peers } => { assert!(!closer_peers.is_empty()); let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::FIND_NODE); @@ -294,16 +352,10 @@ fn msg_to_proto(kad_msg: KadMsg) -> protobuf_structs::dht::Message { } msg } - KadMsg::GetProvidersReq { key } => { - let mut msg = protobuf_structs::dht::Message::new(); - msg.set_field_type(protobuf_structs::dht::Message_MessageType::GET_PROVIDERS); - msg.set_key(key.into_bytes()); - msg.set_clusterLevelRaw(10); - msg - } - KadMsg::GetProvidersRes { closer_peers, provider_peers } => { - // TODO: if empty, the remote will think it's a request - // TODO: not good, possibly exposed in the API + KadResponseMsg::GetProviders { + closer_peers, + provider_peers, + } => { assert!(!closer_peers.is_empty()); let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::GET_PROVIDERS); @@ -316,27 +368,19 @@ fn msg_to_proto(kad_msg: KadMsg) -> protobuf_structs::dht::Message { } msg } - KadMsg::AddProvider { key, provider_peer } => { - let mut msg = protobuf_structs::dht::Message::new(); - msg.set_field_type(protobuf_structs::dht::Message_MessageType::ADD_PROVIDER); - msg.set_clusterLevelRaw(10); - msg.set_key(key.into_bytes()); - msg.mut_providerPeers().push(provider_peer.into()); - msg - } } } /// Turns a raw Kademlia message into a type-safe message. -fn proto_to_msg(mut message: protobuf_structs::dht::Message) -> Result { +fn proto_to_req_msg(mut message: protobuf_structs::dht::Message) -> Result { match message.get_field_type() { - protobuf_structs::dht::Message_MessageType::PING => Ok(KadMsg::Ping), + protobuf_structs::dht::Message_MessageType::PING => Ok(KadRequestMsg::Ping), protobuf_structs::dht::Message_MessageType::PUT_VALUE => { let key = Multihash::from_bytes(message.take_key()) .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))?; let _record = message.take_record(); - Ok(KadMsg::PutValue { + Ok(KadRequestMsg::PutValue { key: key, record: (), }) @@ -345,65 +389,28 @@ fn proto_to_msg(mut message: protobuf_structs::dht::Message) -> Result { let key = Multihash::from_bytes(message.take_key()) .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))?; - Ok(KadMsg::GetValueReq { key: key }) + Ok(KadRequestMsg::GetValue { key: key }) } protobuf_structs::dht::Message_MessageType::FIND_NODE => { - if message.get_closerPeers().is_empty() { - let key = PeerId::from_bytes(message.take_key()) - .map_err(|_| IoError::new(IoErrorKind::InvalidData, "invalid peer id in FIND_NODE"))?; - Ok(KadMsg::FindNodeReq { - key, - }) - - } else { - // TODO: for now we don't parse the peer properly, so it is possible that we get - // parsing errors for peers even when they are valid; we ignore these - // errors for now, but ultimately we should just error altogether - let closer_peers = message.mut_closerPeers() - .iter_mut() - .filter_map(|peer| KadPeer::from_peer(peer).ok()) - .collect::>(); - - Ok(KadMsg::FindNodeRes { - closer_peers, - }) - } + let key = PeerId::from_bytes(message.take_key()).map_err(|_| { + IoError::new(IoErrorKind::InvalidData, "invalid peer id in FIND_NODE") + })?; + Ok(KadRequestMsg::FindNode { key }) } protobuf_structs::dht::Message_MessageType::GET_PROVIDERS => { - if message.get_closerPeers().is_empty() { - let key = Multihash::from_bytes(message.take_key()) - .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))?; - Ok(KadMsg::GetProvidersReq { - key, - }) - - } else { - // TODO: for now we don't parse the peer properly, so it is possible that we get - // parsing errors for peers even when they are valid; we ignore these - // errors for now, but ultimately we should just error altogether - let closer_peers = message.mut_closerPeers() - .iter_mut() - .filter_map(|peer| KadPeer::from_peer(peer).ok()) - .collect::>(); - let provider_peers = message.mut_providerPeers() - .iter_mut() - .filter_map(|peer| KadPeer::from_peer(peer).ok()) - .collect::>(); - - Ok(KadMsg::GetProvidersRes { - closer_peers, - provider_peers, - }) - } + let key = Multihash::from_bytes(message.take_key()) + .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))?; + Ok(KadRequestMsg::GetProviders { key }) } protobuf_structs::dht::Message_MessageType::ADD_PROVIDER => { // TODO: for now we don't parse the peer properly, so it is possible that we get // parsing errors for peers even when they are valid; we ignore these // errors for now, but ultimately we should just error altogether - let provider_peer = message.mut_providerPeers() + let provider_peer = message + .mut_providerPeers() .iter_mut() .filter_map(|peer| KadPeer::from_peer(peer).ok()) .next(); @@ -411,10 +418,7 @@ fn proto_to_msg(mut message: protobuf_structs::dht::Message) -> Result Result Result { + match message.get_field_type() { + protobuf_structs::dht::Message_MessageType::PING => Ok(KadResponseMsg::Pong), + + protobuf_structs::dht::Message_MessageType::GET_VALUE => { + let closer_peers = message + .mut_closerPeers() + .iter_mut() + .filter_map(|peer| KadPeer::from_peer(peer).ok()) + .collect::>(); + + Ok(KadResponseMsg::GetValue { + record: (), + closer_peers, + }) + } + + protobuf_structs::dht::Message_MessageType::FIND_NODE => { + let closer_peers = message + .mut_closerPeers() + .iter_mut() + .filter_map(|peer| KadPeer::from_peer(peer).ok()) + .collect::>(); + + Ok(KadResponseMsg::FindNode { closer_peers }) + } + + protobuf_structs::dht::Message_MessageType::GET_PROVIDERS => { + let closer_peers = message + .mut_closerPeers() + .iter_mut() + .filter_map(|peer| KadPeer::from_peer(peer).ok()) + .collect::>(); + let provider_peers = message + .mut_providerPeers() + .iter_mut() + .filter_map(|peer| KadPeer::from_peer(peer).ok()) + .collect::>(); + + Ok(KadResponseMsg::GetProviders { + closer_peers, + provider_peers, + }) + } + + protobuf_structs::dht::Message_MessageType::PUT_VALUE => Err(IoError::new( + IoErrorKind::InvalidData, + "received an unexpected PUT_VALUE message", + )), + + protobuf_structs::dht::Message_MessageType::ADD_PROVIDER => Err(IoError::new( + IoErrorKind::InvalidData, + "received an unexpected ADD_PROVIDER message", + )), + } +} + #[cfg(test)] mod tests { extern crate libp2p_tcp_transport; extern crate tokio; use self::libp2p_tcp_transport::TcpConfig; + use self::tokio::runtime::current_thread::Runtime; use futures::{Future, Sink, Stream}; - use libp2p_core::{Transport, PeerId}; + use libp2p_core::{PeerId, PublicKey, Transport}; use multihash::{encode, Hash}; - use protocol::{KadConnectionType, KadMsg, KademliaProtocolConfig, KadPeer}; + use protocol::{KadConnectionType, KadPeer, KademliaProtocolConfig}; use std::sync::mpsc; use std::thread; - use self::tokio::runtime::current_thread::Runtime; - + /*// TODO: #[test] fn correct_transfer() { // We open a server and a client, send a message between the two, and check that they were // successfully received. - + test_one(KadMsg::Ping); test_one(KadMsg::PutValue { key: encode(Hash::SHA2256, &[1, 2, 3, 4]).unwrap(), @@ -454,35 +518,29 @@ mod tests { key: encode(Hash::SHA2256, &[10, 11, 12]).unwrap(), }); test_one(KadMsg::FindNodeReq { - key: PeerId::random() + key: PeerId::random(), }); test_one(KadMsg::FindNodeRes { - closer_peers: vec![ - KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], - connection_ty: KadConnectionType::Connected, - }, - ], + closer_peers: vec![KadPeer { + node_id: PeerId::random(), + multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], + connection_ty: KadConnectionType::Connected, + }], }); test_one(KadMsg::GetProvidersReq { key: encode(Hash::SHA2256, &[9, 12, 0, 245, 245, 201, 28, 95]).unwrap(), }); test_one(KadMsg::GetProvidersRes { - closer_peers: vec![ - KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], - connection_ty: KadConnectionType::Connected, - }, - ], - provider_peers: vec![ - KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/200.201.202.203/tcp/1999".parse().unwrap()], - connection_ty: KadConnectionType::NotConnected, - }, - ], + closer_peers: vec![KadPeer { + node_id: PeerId::random(), + multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], + connection_ty: KadConnectionType::Connected, + }], + provider_peers: vec![KadPeer { + node_id: PeerId::random(), + multiaddrs: vec!["/ip4/200.201.202.203/tcp/1999".parse().unwrap()], + connection_ty: KadConnectionType::NotConnected, + }], }); test_one(KadMsg::AddProvider { key: encode(Hash::SHA2256, &[9, 12, 0, 245, 245, 201, 28, 95]).unwrap(), @@ -493,20 +551,19 @@ mod tests { }, }); // TODO: all messages - + fn test_one(msg_server: KadMsg) { let msg_client = msg_server.clone(); let (tx, rx) = mpsc::channel(); - + let bg_thread = thread::spawn(move || { let transport = TcpConfig::new().with_upgrade(KademliaProtocolConfig); - + let (listener, addr) = transport .listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()) .unwrap(); - tx.send(addr).unwrap(); - + let future = listener .into_future() .map_err(|(err, _)| err) @@ -519,9 +576,9 @@ mod tests { let mut rt = Runtime::new().unwrap(); let _ = rt.block_on(future).unwrap(); }); - + let transport = TcpConfig::new().with_upgrade(KademliaProtocolConfig); - + let future = transport .dial(rx.recv().unwrap()) .unwrap_or_else(|_| panic!()) @@ -531,5 +588,5 @@ mod tests { let _ = rt.block_on(future).unwrap(); bg_thread.join().unwrap(); } - } + }*/ } diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index bd6644c91e1..756ae60dc1b 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -18,348 +18,467 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! This module handles performing iterative queries about the network. +//! Contains the iterative querying process of Kademlia. +//! +//! This allows one to create queries that iterate on the DHT on nodes that become closer and +//! closer to the target. -use fnv::FnvHashSet; -use futures::{future, Future, stream, Stream}; +use futures::prelude::*; +use handler::KademliaHandlerIn; use kbucket::KBucketsPeerId; use libp2p_core::PeerId; -use multiaddr::{Protocol, Multiaddr}; -use protocol; -use rand; +use multihash::Multihash; use smallvec::SmallVec; -use std::cmp::Ordering; -use std::io::Error as IoError; -use std::mem; - -/// Parameters of a query. Allows plugging the query-related code with the rest of the -/// infrastructure. -pub struct QueryParams { - /// Identifier of the local peer. - pub local_id: PeerId, - /// Called whenever we need to obtain the peers closest to a certain peer. - pub kbuckets_find_closest: FBuckets, - /// Level of parallelism for networking. If this is `N`, then we can dial `N` nodes at a time. - pub parallelism: usize, - /// Called whenever we want to send a `FIND_NODE` RPC query. - pub find_node: FFindNode, +use std::time::{Duration, Instant}; +use tokio_timer::Delay; + +/// State of a query iterative process. +/// +/// The API of this state machine is similar to the one of `Future`, `Stream` or `Swarm`. You need +/// to call `poll()` to query the state for actions to perform. If `NotReady` is returned, the +/// current task will be woken up automatically when `poll()` needs to be called again. +/// +/// Note that this struct only handles iterating over nodes that are close to the target. For +/// `FIND_NODE` queries you don't need more than that. However for `FIND_VALUE` and +/// `GET_PROVIDERS`, you need to extract yourself the value or list of providers from RPC requests +/// received by remotes as this is not handled by the `QueryState`. +#[derive(Debug)] +pub struct QueryState { + /// Target we're looking for. + target: QueryTarget, + + /// Stage of the query. See the documentation of `QueryStage`. + stage: QueryStage, + + /// Ordered list of the peers closest to the result we're looking for. + /// Entries that are `InProgress` shouldn't be removed from the list before they complete. + /// Must never contain two entries with the same peer IDs. + closest_peers: SmallVec<[(PeerId, QueryPeerState); 32]>, + + /// Allowed level of parallelism. + parallelism: usize, + + /// Number of results to produce. + num_results: usize, + + /// Timeout for each individual RPC query. + rpc_timeout: Duration, } -/// Event that happens during a query. +/// Configuration for a query. #[derive(Debug, Clone)] -pub enum QueryEvent { - /// Learned about new mutiaddresses for the given peers. - PeersReported(Vec<(PeerId, Vec)>), - /// Finished the processing of the query. Contains the result. - Finished(TOut), +pub struct QueryConfig { + /// Target of the query. + pub target: QueryTarget, + + /// Iterator to a list of `num_results` nodes that we know of whose distance is close to the + /// target. + pub known_closest_peers: TIter, + + /// Allowed level of parallelism. + pub parallelism: usize, + + /// Number of results to produce. + pub num_results: usize, + + /// Timeout for each individual RPC query. + pub rpc_timeout: Duration, } -/// Starts a query for an iterative `FIND_NODE` request. -#[inline] -pub fn find_node<'a, FBuckets, FFindNode>( - query_params: QueryParams, - searched_key: PeerId, -) -> Box>, Error = IoError> + Send + 'a> -where - FBuckets: Fn(PeerId) -> Vec + 'a + Clone, - FFindNode: Fn(Multiaddr, PeerId) -> Box, Error = IoError> + Send> + 'a + Clone, -{ - query(query_params, searched_key, 20) // TODO: constant +/// Stage of the query. +#[derive(Debug)] +enum QueryStage { + /// We are trying to find a closest node. + Iterating { + /// Number of successful query results in a row that didn't find any closer node. + // TODO: this is not great, because we don't necessarily receive responses in the order + // we made the queries ; it is possible that we query multiple far-away nodes in a + // row, and obtain results before the result of the closest nodes + no_closer_in_a_row: usize, + }, + + // We have found the closest node, and we are now pinging the nodes we know about. + Frozen, } -/// Refreshes a specific bucket by performing an iterative `FIND_NODE` on a random ID of this -/// bucket. -/// -/// Returns a dummy no-op future if `bucket_num` is out of range. -pub fn refresh<'a, FBuckets, FFindNode>( - query_params: QueryParams, - bucket_num: usize, -) -> Box, Error = IoError> + Send + 'a> -where - FBuckets: Fn(PeerId) -> Vec + 'a + Clone, - FFindNode: Fn(Multiaddr, PeerId) -> Box, Error = IoError> + Send> + 'a + Clone, -{ - let peer_id = match gen_random_id(&query_params.local_id, bucket_num) { - Ok(p) => p, - Err(()) => return Box::new(stream::once(Ok(QueryEvent::Finished(())))), - }; - - let stream = find_node(query_params, peer_id).map(|event| { - match event { - QueryEvent::PeersReported(peers) => QueryEvent::PeersReported(peers), - QueryEvent::Finished(_) => QueryEvent::Finished(()), +impl QueryState { + /// Creates a new query. + /// + /// You should call `poll()` this function returns in order to know what to do. + pub fn new(config: QueryConfig>) -> QueryState { + QueryState { + target: config.target, + stage: QueryStage::Iterating { + no_closer_in_a_row: 0, + }, + closest_peers: config + .known_closest_peers + .into_iter() + .map(|peer_id| (peer_id, QueryPeerState::NotContacted)) + .take(config.num_results) + .collect(), + parallelism: config.parallelism, + num_results: config.num_results, + rpc_timeout: config.rpc_timeout, } - }); - - Box::new(stream) as Box<_> -} + } -// Generates a random `PeerId` that belongs to the given bucket. -// -// Returns an error if `bucket_num` is out of range. -fn gen_random_id(my_id: &PeerId, bucket_num: usize) -> Result { - let my_id_len = my_id.as_bytes().len(); - - // TODO: this 2 is magic here; it is the length of the hash of the multihash - let bits_diff = bucket_num + 1; - if bits_diff > 8 * (my_id_len - 2) { - return Err(()); + /// Returns the target of the query. Always the same as what was passed to `new()`. + #[inline] + pub fn target(&self) -> &QueryTarget { + &self.target } - let mut random_id = [0; 64]; - for byte in 0..my_id_len { - match byte.cmp(&(my_id_len - bits_diff / 8 - 1)) { - Ordering::Less => { - random_id[byte] = my_id.as_bytes()[byte]; - } - Ordering::Equal => { - let mask: u8 = (1 << (bits_diff % 8)) - 1; - random_id[byte] = (my_id.as_bytes()[byte] & !mask) | (rand::random::() & mask); - } - Ordering::Greater => { - random_id[byte] = rand::random(); + /// After `poll()` returned `SendRpc`, this method should be called when the node sends back + /// the result of the query. + /// + /// Note that if this query is a `FindValue` query and a node returns a record, feel free to + /// immediately drop the query altogether and use the record. + /// + /// After this function returns, you should call `poll()` again. + pub fn inject_rpc_result( + &mut self, + result_source: &PeerId, + closer_peers: impl IntoIterator, + ) { + // Mark the peer as succeeded. + for (peer_id, state) in self.closest_peers.iter_mut() { + if peer_id == result_source { + match state { + state @ QueryPeerState::InProgress(_) => *state = QueryPeerState::Succeeded, + _ => (), + } } } - } - let peer_id = PeerId::from_bytes(random_id[..my_id_len].to_owned()) - .expect("randomly-generated peer ID should always be valid"); - Ok(peer_id) -} + // Add the entries in `closest_peers`. + if let QueryStage::Iterating { + ref mut no_closer_in_a_row, + } = self.stage + { + // We increment now, and reset to 0 if we find a closer node. + *no_closer_in_a_row += 1; + + for elem_to_add in closer_peers { + let target = &self.target; + let insert_pos = self.closest_peers.iter().position(|(id, _)| { + let a = target.as_hash().distance_with(id.as_ref()); + let b = target.as_hash().distance_with(elem_to_add.as_ref()); + a >= b + }); + + if let Some(insert_pos) = insert_pos { + // Make sure we don't insert duplicates. + if self.closest_peers[insert_pos].0 != elem_to_add { + if insert_pos == 0 { + *no_closer_in_a_row = 0; + } + self.closest_peers + .insert(insert_pos, (elem_to_add, QueryPeerState::NotContacted)); + } + } else if self.closest_peers.len() < self.num_results { + self.closest_peers + .push((elem_to_add, QueryPeerState::NotContacted)); + } + } + } -// Generic query-performing function. -fn query<'a, FBuckets, FFindNode>( - query_params: QueryParams, - searched_key: PeerId, - num_results: usize, -) -> Box>, Error = IoError> + Send + 'a> -where - FBuckets: Fn(PeerId) -> Vec + 'a + Clone, - FFindNode: Fn(Multiaddr, PeerId) -> Box, Error = IoError> + Send> + 'a + Clone, -{ - debug!("Start query for {:?}; num results = {}", searched_key, num_results); - - // State of the current iterative process. - struct State<'a> { - // At which stage we are. - stage: Stage, - // Final output of the iteration. - result: Vec, - // For each open connection, a future with the response of the remote. - // Note that don't use a `SmallVec` here because `select_all` produces a `Vec`. - current_attempts_fut: Vec, Error = IoError> + Send + 'a>>, - // For each open connection, the peer ID that we are connected to. - // Must always have the same length as `current_attempts_fut`. - current_attempts_addrs: SmallVec<[PeerId; 32]>, - // Nodes that need to be attempted. - pending_nodes: Vec, - // Peers that we tried to contact but failed. - failed_to_contact: FnvHashSet, + // Handle if `no_closer_in_a_row` is too high. + let freeze = if let QueryStage::Iterating { no_closer_in_a_row } = self.stage { + no_closer_in_a_row >= self.parallelism + } else { + false + }; + if freeze { + self.stage = QueryStage::Frozen; + } } - // General stage of the state. - #[derive(Copy, Clone, PartialEq, Eq)] - enum Stage { - // We are still in the first step of the algorithm where we try to find the closest node. - FirstStep, - // We are contacting the k closest nodes in order to fill the list with enough results. - SecondStep, - // The results are complete, and the next stream iteration will produce the outcome. - FinishingNextIter, - // We are finished and the stream shouldn't return anything anymore. - Finished, + /// After `poll()` returned `SendRpc`, this function should be called if we were unable to + /// reach the peer, or if an error of some sort happened. + /// + /// Has no effect if the peer ID is not relevant to the query, so feel free to call this + /// function whenever an error happens on the network. + /// + /// After this function returns, you should call `poll()` again. + pub fn inject_rpc_error(&mut self, id: &PeerId) { + let state = self + .closest_peers + .iter_mut() + .filter_map( + |(peer_id, state)| { + if peer_id == id { + Some(state) + } else { + None + } + }, + ) + .next(); + + match state { + Some(state @ &mut QueryPeerState::InProgress(_)) => *state = QueryPeerState::Failed, + Some(&mut QueryPeerState::NotContacted) => (), + Some(&mut QueryPeerState::Succeeded) => (), + Some(&mut QueryPeerState::Failed) => (), + None => (), + } } - let initial_state = State { - stage: Stage::FirstStep, - result: Vec::with_capacity(num_results), - current_attempts_fut: Vec::new(), - current_attempts_addrs: SmallVec::new(), - pending_nodes: { - let kbuckets_find_closest = query_params.kbuckets_find_closest.clone(); - kbuckets_find_closest(searched_key.clone()) // TODO: suboptimal - }, - failed_to_contact: Default::default(), - }; - - let parallelism = query_params.parallelism; - - // Start of the iterative process. - let stream = stream::unfold(initial_state, move |mut state| -> Option<_> { - match state.stage { - Stage::FinishingNextIter => { - let result = mem::replace(&mut state.result, Vec::new()); - debug!("Query finished with {} results", result.len()); - state.stage = Stage::Finished; - let future = future::ok((Some(QueryEvent::Finished(result)), state)); - return Some(future::Either::A(future)); - }, - Stage::Finished => { - return None; - }, - _ => () - }; + /// Polls this individual query. + pub fn poll(&mut self) -> Async { + // While iterating over peers, count the number of queries currently being processed. + let mut active_counter = 0; + // While iterating over peers, count the number of queries in a row that succeeded. + // Contains `None` if the chain is broken. + let mut succeeded_counter = Some(0); - let searched_key = searched_key.clone(); - let find_node_rpc = query_params.find_node.clone(); + // Extract `self.num_results` to avoid multiple borrowing errors with closures. + let num_results = self.num_results; - // Find out which nodes to contact at this iteration. - let to_contact = { - let wanted_len = if state.stage == Stage::FirstStep { - parallelism.saturating_sub(state.current_attempts_fut.len()) - } else { - num_results.saturating_sub(state.current_attempts_fut.len()) - }; - let mut to_contact = SmallVec::<[_; 16]>::new(); - while to_contact.len() < wanted_len && !state.pending_nodes.is_empty() { - // Move the first element of `pending_nodes` to `to_contact`, but ignore nodes that - // are already part of the results or of a current attempt or if we failed to - // contact it before. - let peer = state.pending_nodes.remove(0); - if state.result.iter().any(|p| p == &peer) { - continue; + for &mut (ref peer_id, ref mut state) in self.closest_peers.iter_mut() { + // Start by "killing" the query if it timed out. + { + let timed_out = match state { + QueryPeerState::InProgress(timeout) => match timeout.poll() { + Ok(Async::Ready(_)) | Err(_) => true, + Ok(Async::NotReady) => false, + }, + _ => false, + }; + if timed_out { + *state = QueryPeerState::Failed; + return Async::Ready(QueryStatePollOut::CancelRpc { peer_id }); } - if state.current_attempts_addrs.iter().any(|p| p == &peer) { - continue; + } + + // Increment the counters. + match state { + QueryPeerState::InProgress(_) => { + active_counter += 1; } - if state.failed_to_contact.iter().any(|p| p == &peer) { - continue; + QueryPeerState::Succeeded => { + if let Some(ref mut c) = succeeded_counter { + *c += 1; + } } - to_contact.push(peer); + _ => (), + }; + + // We have enough results ; the query is done. + if succeeded_counter + .as_ref() + .map(|&c| c >= num_results) + .unwrap_or(false) + { + return Async::Ready(QueryStatePollOut::Finished); } - to_contact - }; - debug!("New query round; {} queries in progress; contacting {} new peers", - state.current_attempts_fut.len(), - to_contact.len()); - - // For each node in `to_contact`, start an RPC query and a corresponding entry in the two - // `state.current_attempts_*` fields. - for peer in to_contact { - let multiaddr: Multiaddr = Protocol::P2p(peer.clone().into_bytes()).into(); - - let searched_key2 = searched_key.clone(); - let current_attempt = find_node_rpc(multiaddr.clone(), searched_key2); // TODO: suboptimal - state.current_attempts_addrs.push(peer.clone()); - state - .current_attempts_fut - .push(Box::new(current_attempt) as Box<_>); + // Dial nodes that need dialing. + let need_connect = match state { + QueryPeerState::NotContacted => match self.stage { + QueryStage::Iterating { .. } => active_counter < self.parallelism, + QueryStage::Frozen => true, + }, + _ => false, + }; + if need_connect { + let delay = Delay::new(Instant::now() + self.rpc_timeout); + *state = QueryPeerState::InProgress(delay); + return Async::Ready(QueryStatePollOut::SendRpc { + peer_id, + query_target: &self.target, + }); + } } - debug_assert_eq!( - state.current_attempts_addrs.len(), - state.current_attempts_fut.len() - ); - - // Extract `current_attempts_fut` so that we can pass it to `select_all`. We will push the - // values back when inside the loop. - let current_attempts_fut = mem::replace(&mut state.current_attempts_fut, Vec::new()); - if current_attempts_fut.is_empty() { - // If `current_attempts_fut` is empty, then `select_all` would panic. It happens - // when we have no additional node to query. - debug!("Finishing query early because no additional node available"); - state.stage = Stage::FinishingNextIter; - let future = future::ok((None, state)); - return Some(future::Either::A(future)); + + // If we don't have any query in progress, return `Finished` as we don't have anything more + // we can do. + if active_counter > 0 { + Async::NotReady + } else { + Async::Ready(QueryStatePollOut::Finished) } + } - // This is the future that continues or breaks the `loop_fn`. - let future = future::select_all(current_attempts_fut.into_iter()).then(move |result| { - let (message, trigger_idx, other_current_attempts) = match result { - Err((err, trigger_idx, other_current_attempts)) => { - (Err(err), trigger_idx, other_current_attempts) - } - Ok((message, trigger_idx, other_current_attempts)) => { - (Ok(message), trigger_idx, other_current_attempts) + /// Consumes the query and returns the known closest peers. + pub fn into_closest_peers(self) -> impl Iterator { + self.closest_peers + .into_iter() + .filter_map(|(peer_id, state)| { + if let QueryPeerState::Succeeded = state { + Some(peer_id) + } else { + None } - }; + }) + .take(self.num_results) + } +} - // Putting back the extracted elements in `state`. - let remote_id = state.current_attempts_addrs.remove(trigger_idx); - debug_assert!(state.current_attempts_fut.is_empty()); - state.current_attempts_fut = other_current_attempts; - - // `message` contains the reason why the current future was woken up. - let closer_peers = match message { - Ok(msg) => msg, - Err(err) => { - trace!("RPC query failed for {:?}: {:?}", remote_id, err); - state.failed_to_contact.insert(remote_id); - return future::ok((None, state)); - } - }; +/// Outcome of polling a query. +#[derive(Debug, Clone)] +pub enum QueryStatePollOut<'a> { + /// The query is finished. + /// + /// If this is a `FindValue` query, the user is supposed to extract the record themselves from + /// any RPC result sent by a remote. If the query finished without that happening, this means + /// that we didn't find any record. + /// Similarly, if this is a `GetProviders` query, the user is supposed to extract the providers + /// from any RPC result sent by a remote. + /// + /// If this is a `FindNode` query, you can call `into_closest_peers` in order to obtain the + /// result. + Finished, + + /// We need to send an RPC query to the given peer. + /// + /// The RPC query to send can be derived from the target of the query. + /// + /// After this has been returned, you should call either `inject_rpc_result` or + /// `inject_rpc_error` at a later point in time. + SendRpc { + /// The peer to send the RPC query to. + peer_id: &'a PeerId, + /// A reminder of the query target. Same as what you obtain by calling `target()`. + query_target: &'a QueryTarget, + }, + + /// We no longer need to send a query to this specific node. + /// + /// It is guaranteed that an earlier polling returned `SendRpc` with this peer id. + CancelRpc { + /// The target. + peer_id: &'a PeerId, + }, +} - // Inserting the node we received a response from into `state.result`. - // The code is non-trivial because `state.result` is ordered by distance and is limited - // by `num_results` elements. - if let Some(insert_pos) = state.result.iter().position(|e| { - e.distance_with(&searched_key) >= remote_id.distance_with(&searched_key) - }) { - if state.result[insert_pos] != remote_id { - if state.result.len() >= num_results { - state.result.pop(); - } - state.result.insert(insert_pos, remote_id); - } - } else if state.result.len() < num_results { - state.result.push(remote_id); - } +/// What we're aiming for with our query. +#[derive(Debug, Clone)] +pub enum QueryTarget { + /// Finding a peer. + FindPeer(PeerId), + /// Find a value. + FindValue(Multihash), + /// Find the peers that provide a certain value. + GetProviders(Multihash), +} - // The loop below will set this variable to `true` if we find a new element to put at - // the top of the result. This would mean that we have to continue looping. - let mut local_nearest_node_updated = false; - - // Update `state` with the actual content of the message. - let mut new_known_multiaddrs = Vec::with_capacity(closer_peers.len()); - for mut peer in closer_peers { - // Update the peerstore with the information sent by - // the remote. - { - let multiaddrs = mem::replace(&mut peer.multiaddrs, Vec::new()); - trace!("Reporting multiaddresses for {:?}: {:?}", peer.node_id, multiaddrs); - new_known_multiaddrs.push((peer.node_id.clone(), multiaddrs)); - } +impl QueryTarget { + /// Creates the corresponding RPC request to send to remote. + pub fn to_rpc_request(&self, user_data: TUserData) -> KademliaHandlerIn { + match self { + QueryTarget::FindPeer(peer) => KademliaHandlerIn::FindNodeReq { + key: peer.clone(), + user_data, + }, + QueryTarget::FindValue(_) => { + // TODO: no `KademliaHandlerIn` variant exists yet, as this is not implemented + unimplemented!() + }, + QueryTarget::GetProviders(key) => KademliaHandlerIn::GetProvidersReq { + key: key.clone(), + user_data, + }, + } + } - if peer.node_id.distance_with(&searched_key) - <= state.result[0].distance_with(&searched_key) - { - local_nearest_node_updated = true; - } + /// Returns the hash of the thing we're looking for. + pub fn as_hash(&self) -> &Multihash { + match self { + QueryTarget::FindPeer(peer) => peer.as_ref(), + QueryTarget::FindValue(key) => key, + QueryTarget::GetProviders(key) => key, + } + } +} - if state.result.iter().any(|ma| ma == &peer.node_id) { - continue; +/// State of peer in the context of a query. +#[derive(Debug)] +enum QueryPeerState { + /// We haven't tried contacting the node. + NotContacted, + /// Waiting for an answer from the node to our RPC query. Includes a timeout. + InProgress(Delay), + /// We successfully reached the node. + Succeeded, + /// We tried to reach the node but failed. + Failed, +} + +#[cfg(test)] +mod tests { + use super::{QueryConfig, QueryState, QueryStatePollOut, QueryTarget}; + use futures::{self, prelude::*}; + use libp2p_core::PeerId; + use std::{iter, time::Duration, sync::Arc, sync::Mutex}; + use tokio; + + #[test] + fn start_by_sending_rpc_to_known_peers() { + let random_id = PeerId::random(); + let target = QueryTarget::FindPeer(PeerId::random()); + + let mut query = QueryState::new(QueryConfig { + target, + known_closest_peers: iter::once(random_id.clone()), + parallelism: 3, + num_results: 100, + rpc_timeout: Duration::from_secs(10), + }); + + tokio::run(futures::future::poll_fn(move || { + match try_ready!(Ok(query.poll())) { + QueryStatePollOut::SendRpc { peer_id, .. } if peer_id == &random_id => { + Ok(Async::Ready(())) } + _ => panic!(), + } + })); + } - // Insert the node into `pending_nodes` at the right position, or do not - // insert it if it is already in there. - if let Some(insert_pos) = state.pending_nodes.iter().position(|e| { - e.distance_with(&searched_key) >= peer.node_id.distance_with(&searched_key) - }) { - if state.pending_nodes[insert_pos] != peer.node_id { - state.pending_nodes.insert(insert_pos, peer.node_id.clone()); + #[test] + fn continue_second_result() { + let random_id = PeerId::random(); + let random_id2 = PeerId::random(); + let target = QueryTarget::FindPeer(PeerId::random()); + + let query = Arc::new(Mutex::new(QueryState::new(QueryConfig { + target, + known_closest_peers: iter::once(random_id.clone()), + parallelism: 3, + num_results: 100, + rpc_timeout: Duration::from_secs(10), + }))); + + // Let's do a first polling round to obtain the `SendRpc` request. + tokio::run(futures::future::poll_fn({ + let random_id = random_id.clone(); + let query = query.clone(); + move || { + match try_ready!(Ok(query.lock().unwrap().poll())) { + QueryStatePollOut::SendRpc { peer_id, .. } if peer_id == &random_id => { + Ok(Async::Ready(())) } - } else { - state.pending_nodes.push(peer.node_id.clone()); + _ => panic!(), } } - - if state.result.len() >= num_results - || (state.stage != Stage::FirstStep && state.current_attempts_fut.is_empty()) - { - state.stage = Stage::FinishingNextIter; - - } else { - if !local_nearest_node_updated { - trace!("Loop didn't update closer node; jumping to step 2"); - state.stage = Stage::SecondStep; + })); + + // Send the reply. + query.lock().unwrap().inject_rpc_result(&random_id, iter::once(random_id2.clone())); + + // Second polling round to check the second `SendRpc` request. + tokio::run(futures::future::poll_fn({ + let query = query.clone(); + move || { + match try_ready!(Ok(query.lock().unwrap().poll())) { + QueryStatePollOut::SendRpc { peer_id, .. } if peer_id == &random_id2 => { + Ok(Async::Ready(())) + } + _ => panic!(), } } - - future::ok((Some(QueryEvent::PeersReported(new_known_multiaddrs)), state)) - }); - - Some(future::Either::B(future)) - }).filter_map(|val| val); - - Box::new(stream) as Box<_> + })); + } } diff --git a/protocols/kad/src/topology.rs b/protocols/kad/src/topology.rs new file mode 100644 index 00000000000..94dc16536ed --- /dev/null +++ b/protocols/kad/src/topology.rs @@ -0,0 +1,73 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use kbucket::KBucketsPeerId; +use libp2p_core::{Multiaddr, PeerId, topology::MemoryTopology, topology::Topology}; +use multihash::Multihash; +use protocol::KadConnectionType; +use std::vec; + +/// Trait allowing retreival of information necessary for the Kadmelia system to work. +pub trait KademliaTopology: Topology { + /// Iterator returned by `closest_peers`. + type ClosestPeersIter: Iterator; + + /// Iterator returned by `get_providers`. + type GetProvidersIter: Iterator; + + /// Adds an address discovered through Kademlia to the topology. + fn add_kad_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr, + connection_ty: KadConnectionType); + + /// Returns the known peers closest by XOR distance to the `target`. + /// + /// The `max` parameter is the maximum number of results that we are going to use. + fn closest_peers(&mut self, target: &Multihash, max: usize) -> Self::ClosestPeersIter; + + /// Registers the given peer as provider of the resource with the given ID. + fn add_provider(&mut self, key: Multihash, peer_id: PeerId); + + /// Returns the list of providers that have been registered. + fn get_providers(&mut self, key: &Multihash) -> Self::GetProvidersIter; +} + +// TODO: stupid idea to implement on `MemoryTopology` +impl KademliaTopology for MemoryTopology { + type ClosestPeersIter = vec::IntoIter; + type GetProvidersIter = vec::IntoIter; + + fn add_kad_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr, _: KadConnectionType) { + Topology::add_discovered_address(self, peer, addr) + } + + fn closest_peers(&mut self, target: &Multihash, _: usize) -> Self::ClosestPeersIter { + let mut list = self.peers(); + list.sort_by(|a, b| target.distance_with(b.as_ref()).cmp(&target.distance_with(a.as_ref()))); + list.into_iter() + } + + fn add_provider(&mut self, _: Multihash, _: PeerId) { + unimplemented!() + } + + fn get_providers(&mut self, _: &Multihash) -> Self::GetProvidersIter { + unimplemented!() + } +} diff --git a/protocols/ping/src/dial_layer.rs b/protocols/ping/src/dial_layer.rs index b23b96774e6..3d7a1251c6e 100644 --- a/protocols/ping/src/dial_layer.rs +++ b/protocols/ping/src/dial_layer.rs @@ -48,7 +48,7 @@ impl Default for PeriodicPingBehaviour { } } -impl NetworkBehaviour for PeriodicPingBehaviour +impl NetworkBehaviour for PeriodicPingBehaviour where TSubstream: AsyncRead + AsyncWrite, { @@ -72,6 +72,7 @@ where fn poll( &mut self, + _: &mut TTopology, ) -> Async< NetworkBehaviourAction< ::InEvent, diff --git a/protocols/ping/src/listen_layer.rs b/protocols/ping/src/listen_layer.rs index f23f40e44a0..da5d66f2fde 100644 --- a/protocols/ping/src/listen_layer.rs +++ b/protocols/ping/src/listen_layer.rs @@ -48,7 +48,7 @@ impl Default for PingListenBehaviour { } } -impl NetworkBehaviour for PingListenBehaviour +impl NetworkBehaviour for PingListenBehaviour where TSubstream: AsyncRead + AsyncWrite, { @@ -72,6 +72,7 @@ where fn poll( &mut self, + _: &mut TTopology, ) -> Async< NetworkBehaviourAction< ::InEvent, From d85ba71dc313da94042bdf13556b6a9ec88293bd Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Mon, 26 Nov 2018 14:49:56 +0100 Subject: [PATCH 02/11] Minor work on protocol.rs --- protocols/kad/src/protocol.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index f2787d98541..f5e7d66796e 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -38,6 +38,7 @@ use tokio_codec::Framed; use tokio_io::{AsyncRead, AsyncWrite}; use unsigned_varint::codec; +/// Status of our connection to a node reported by the Kademlia protocol. #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum KadConnectionType { /// Sender hasn't tried to connect to peer. @@ -79,9 +80,11 @@ impl Into for KadConnectionType { /// Information about a peer, as known by the sender. #[derive(Debug, Clone, PartialEq, Eq)] pub struct KadPeer { + /// Identifier of the peer. pub node_id: PeerId, - /// The multiaddresses that are known for that peer. + /// The multiaddresses that the sender think can be used in order to reach the peer. pub multiaddrs: Vec, + /// How the sender is connected to that remote. pub connection_ty: KadConnectionType, } @@ -126,6 +129,9 @@ impl Into for KadPeer { /// Configuration for a Kademlia connection upgrade. When applied to a connection, turns this /// connection into a `Stream + Sink` whose items are of type `KadRequestMsg` and `KadResponseMsg`. +// TODO: if, as suspected, we can confirm with Protocol Labs that each open Kademlia substream does +// only one request, then we can change the output of the `InboundUpgrade` and +// `OutboundUpgrade` to be just a single message #[derive(Debug, Default, Copy, Clone)] pub struct KademliaProtocolConfig; @@ -203,6 +209,7 @@ pub type KadInStreamSink = stream::AndThen< fn(BytesMut) -> Result, Result, >; + /// Sink of requests and stream of responses. pub type KadOutStreamSink = stream::AndThen< sink::With< @@ -216,7 +223,6 @@ pub type KadOutStreamSink = stream::AndThen< >; /// Request that we can send to a peer or that we received from a peer. -// TODO: document the rest #[derive(Debug, Clone, PartialEq, Eq)] pub enum KadRequestMsg { /// Ping request. @@ -230,6 +236,7 @@ pub enum KadRequestMsg { record: (), //record: protobuf_structs::record::Record, // TODO: no }, + /// Request for a value stored in the DHT. GetValue { /// Identifier of the record. key: Multihash, @@ -503,7 +510,7 @@ mod tests { use std::sync::mpsc; use std::thread; - /*// TODO: + /*// TODO: restore #[test] fn correct_transfer() { // We open a server and a client, send a message between the two, and check that they were From 087c98d694911632f243dc80dae9bb1dc6900d1b Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 27 Nov 2018 11:59:12 +0100 Subject: [PATCH 03/11] More work --- examples/ipfs-kad.rs | 9 ++-- protocols/kad/src/behaviour.rs | 2 +- protocols/kad/src/handler.rs | 29 +------------ protocols/kad/src/protocol.rs | 75 ++++++---------------------------- 4 files changed, 21 insertions(+), 94 deletions(-) diff --git a/examples/ipfs-kad.rs b/examples/ipfs-kad.rs index dffa055a9dc..b296562f1f9 100644 --- a/examples/ipfs-kad.rs +++ b/examples/ipfs-kad.rs @@ -66,10 +66,13 @@ fn main() { // Create a swarm to manage peers and events. let mut swarm = { - // Create a Kademlia behaviour ; note that we use `without_init` because this example is - // very ephemeral. In a real world application, you want to use `new` instead. + // Create a Kademlia behaviour. + // Note that normally the Kademlia process starts by performing lots of request in order + // to insert our local node in the DHT. However here we use `without_init` because this + // example is very ephemeral and we don't want to pollute the DHT. In a real world + // application, you want to use `new` instead. let mut behaviour = libp2p::kad::Kademlia::without_init(local_peer_id); - libp2p::core::nodes::Swarm::new(transport, behaviour, topology) + libp2p::core::Swarm::new(transport, behaviour, topology) }; // Order Kademlia to search for a peer. diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index fff9dbe44ec..11b8b381398 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -21,7 +21,7 @@ use fnv::{FnvHashMap, FnvHashSet}; use futures::prelude::*; use handler::{KademliaHandler, KademliaHandlerEvent, KademliaHandlerIn, KademliaRequestId}; -use libp2p_core::nodes::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction}; +use libp2p_core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction}; use libp2p_core::{protocols_handler::ProtocolsHandler, topology::Topology, Multiaddr, PeerId}; use multihash::Multihash; use protocol::{KadConnectionType, KadPeer}; diff --git a/protocols/kad/src/handler.rs b/protocols/kad/src/handler.rs index d0154b28e0c..264d23c4764 100644 --- a/protocols/kad/src/handler.rs +++ b/protocols/kad/src/handler.rs @@ -692,31 +692,17 @@ fn process_kad_request( ) -> Result, io::Error> { match event { KadRequestMsg::Ping => { - // TODO: implement + // TODO: implement ; in practice the PING message is never used, so we may consider + // removing it altogether Err(io::Error::new( io::ErrorKind::InvalidData, "the PING Kademlia message is not implemented", )) } - KadRequestMsg::PutValue { .. } => { - // TODO: implement - Err(io::Error::new( - io::ErrorKind::InvalidData, - "the PUT_VALUE Kademlia message is not implemented", - )) - } - KadRequestMsg::FindNode { key } => Ok(KademliaHandlerEvent::FindNodeReq { key, request_id: KademliaRequestId { connec_unique_id }, }), - KadRequestMsg::GetValue { .. } => { - // TODO: implement - Err(io::Error::new( - io::ErrorKind::InvalidData, - "the GET_VALUE Kademlia message is not implemented", - )) - } KadRequestMsg::GetProviders { key } => Ok(KademliaHandlerEvent::GetProvidersReq { key, request_id: KademliaRequestId { connec_unique_id }, @@ -745,17 +731,6 @@ fn process_kad_response( user_data, } } - KadResponseMsg::GetValue { .. } => { - // TODO: implement - let err = io::Error::new( - io::ErrorKind::InvalidData, - "received unexpected GET_VALUE response", - ); - KademliaHandlerEvent::QueryError { - error: err, - user_data, - } - } KadResponseMsg::FindNode { closer_peers } => KademliaHandlerEvent::FindNodeRes { closer_peers, user_data, diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index f5e7d66796e..d1976870879 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -228,20 +228,6 @@ pub enum KadRequestMsg { /// Ping request. Ping, - /// Target must save the given record, can be queried later with `GetValueReq`. - PutValue { - /// Identifier of the record. - key: Multihash, - /// The record itself. - record: (), //record: protobuf_structs::record::Record, // TODO: no - }, - - /// Request for a value stored in the DHT. - GetValue { - /// Identifier of the record. - key: Multihash, - }, - /// Request for the list of nodes whose IDs are the closest to `key`. The number of nodes /// returned is not specified, but should be around 20. FindNode { @@ -266,18 +252,11 @@ pub enum KadRequestMsg { } /// Response that we can send to a peer or that we received from a peer. -// TODO: document the rest #[derive(Debug, Clone, PartialEq, Eq)] pub enum KadResponseMsg { /// Ping response. Pong, - /// Response to a `GetValue`. - GetValue { - record: (), //record: Option, // TODO: no - closer_peers: Vec, - }, - /// Response to a `FindNode`. FindNode { /// Results of the request. @@ -301,20 +280,6 @@ fn req_msg_to_proto(kad_msg: KadRequestMsg) -> protobuf_structs::dht::Message { msg.set_field_type(protobuf_structs::dht::Message_MessageType::PING); msg } - KadRequestMsg::PutValue { key, .. } => { - let mut msg = protobuf_structs::dht::Message::new(); - msg.set_field_type(protobuf_structs::dht::Message_MessageType::PUT_VALUE); - msg.set_key(key.into_bytes()); - //msg.set_record(record); // TODO: - msg - } - KadRequestMsg::GetValue { key } => { - let mut msg = protobuf_structs::dht::Message::new(); - msg.set_field_type(protobuf_structs::dht::Message_MessageType::GET_VALUE); - msg.set_key(key.into_bytes()); - msg.set_clusterLevelRaw(10); - msg - } KadRequestMsg::FindNode { key } => { let mut msg = protobuf_structs::dht::Message::new(); msg.set_field_type(protobuf_structs::dht::Message_MessageType::FIND_NODE); @@ -348,7 +313,6 @@ fn resp_msg_to_proto(kad_msg: KadResponseMsg) -> protobuf_structs::dht::Message msg.set_field_type(protobuf_structs::dht::Message_MessageType::PING); msg } - KadResponseMsg::GetValue { .. } => unimplemented!(), // TODO: KadResponseMsg::FindNode { closer_peers } => { assert!(!closer_peers.is_empty()); let mut msg = protobuf_structs::dht::Message::new(); @@ -384,19 +348,17 @@ fn proto_to_req_msg(mut message: protobuf_structs::dht::Message) -> Result Ok(KadRequestMsg::Ping), protobuf_structs::dht::Message_MessageType::PUT_VALUE => { - let key = Multihash::from_bytes(message.take_key()) - .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))?; - let _record = message.take_record(); - Ok(KadRequestMsg::PutValue { - key: key, - record: (), - }) + Err(IoError::new( + IoErrorKind::InvalidData, + "received a PUT_VALUE message, but this is not supported by rust-libp2p yet", + )) } protobuf_structs::dht::Message_MessageType::GET_VALUE => { - let key = Multihash::from_bytes(message.take_key()) - .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))?; - Ok(KadRequestMsg::GetValue { key: key }) + Err(IoError::new( + IoErrorKind::InvalidData, + "received a GET_VALUE message, but this is not supported by rust-libp2p yet", + )) } protobuf_structs::dht::Message_MessageType::FIND_NODE => { @@ -444,16 +406,10 @@ fn proto_to_resp_msg( protobuf_structs::dht::Message_MessageType::PING => Ok(KadResponseMsg::Pong), protobuf_structs::dht::Message_MessageType::GET_VALUE => { - let closer_peers = message - .mut_closerPeers() - .iter_mut() - .filter_map(|peer| KadPeer::from_peer(peer).ok()) - .collect::>(); - - Ok(KadResponseMsg::GetValue { - record: (), - closer_peers, - }) + Err(IoError::new( + IoErrorKind::InvalidData, + "received a GET_VALUE message, but this is not supported by rust-libp2p yet", + )) } protobuf_structs::dht::Message_MessageType::FIND_NODE => { @@ -517,13 +473,6 @@ mod tests { // successfully received. test_one(KadMsg::Ping); - test_one(KadMsg::PutValue { - key: encode(Hash::SHA2256, &[1, 2, 3, 4]).unwrap(), - record: (), - }); - test_one(KadMsg::GetValueReq { - key: encode(Hash::SHA2256, &[10, 11, 12]).unwrap(), - }); test_one(KadMsg::FindNodeReq { key: PeerId::random(), }); From f207936a257aa64a5cbe54062bb97964c401d3d1 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 27 Nov 2018 12:04:20 +0100 Subject: [PATCH 04/11] Remove QueryTarget::FindValue --- protocols/kad/src/behaviour.rs | 3 --- protocols/kad/src/query.rs | 7 ------- 2 files changed, 10 deletions(-) diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 11b8b381398..24e103d25e7 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -480,9 +480,6 @@ where provider_peers: Vec::new(), // FIXME: } }, - QueryTarget::FindValue(key) => { - unimplemented!() // TODO: - }, }; break Async::Ready(NetworkBehaviourAction::GenerateEvent(event)); diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index 756ae60dc1b..fee2c7f0bb4 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -358,8 +358,6 @@ pub enum QueryStatePollOut<'a> { pub enum QueryTarget { /// Finding a peer. FindPeer(PeerId), - /// Find a value. - FindValue(Multihash), /// Find the peers that provide a certain value. GetProviders(Multihash), } @@ -372,10 +370,6 @@ impl QueryTarget { key: peer.clone(), user_data, }, - QueryTarget::FindValue(_) => { - // TODO: no `KademliaHandlerIn` variant exists yet, as this is not implemented - unimplemented!() - }, QueryTarget::GetProviders(key) => KademliaHandlerIn::GetProvidersReq { key: key.clone(), user_data, @@ -387,7 +381,6 @@ impl QueryTarget { pub fn as_hash(&self) -> &Multihash { match self { QueryTarget::FindPeer(peer) => peer.as_ref(), - QueryTarget::FindValue(key) => key, QueryTarget::GetProviders(key) => key, } } From 5ee331520d677fc43ea99ecd8c74f51efb47713e Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 27 Nov 2018 12:15:31 +0100 Subject: [PATCH 05/11] Finish work on query --- protocols/kad/src/query.rs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index fee2c7f0bb4..c24f9e247ed 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -228,12 +228,16 @@ impl QueryState { /// Polls this individual query. pub fn poll(&mut self) -> Async { // While iterating over peers, count the number of queries currently being processed. + // This is used to not go over the limit of parallel requests. + // If this is still 0 at the end of the function, that means the query is finished. let mut active_counter = 0; - // While iterating over peers, count the number of queries in a row that succeeded. + + // While iterating over peers, count the number of queries in a row (from closer to further + // away from target) that are in the succeeded in state. // Contains `None` if the chain is broken. let mut succeeded_counter = Some(0); - // Extract `self.num_results` to avoid multiple borrowing errors with closures. + // Extract `self.num_results` to avoid borrowing errors with closures. let num_results = self.num_results; for &mut (ref peer_id, ref mut state) in self.closest_peers.iter_mut() { @@ -252,7 +256,7 @@ impl QueryState { } } - // Increment the counters. + // Increment the local counters. match state { QueryPeerState::InProgress(_) => { active_counter += 1; @@ -274,14 +278,18 @@ impl QueryState { return Async::Ready(QueryStatePollOut::Finished); } - // Dial nodes that need dialing. + // Dial the node if it needs dialing. let need_connect = match state { QueryPeerState::NotContacted => match self.stage { QueryStage::Iterating { .. } => active_counter < self.parallelism, - QueryStage::Frozen => true, + QueryStage::Frozen => match self.target { + QueryTarget::FindPeer(_) => true, + QueryTarget::GetProviders(_) => false, + }, }, _ => false, }; + if need_connect { let delay = Delay::new(Instant::now() + self.rpc_timeout); *state = QueryPeerState::InProgress(delay); @@ -302,6 +310,9 @@ impl QueryState { } /// Consumes the query and returns the known closest peers. + /// + /// > **Note**: This can be called at any time, but you normally only do that once the query + /// > is finished. pub fn into_closest_peers(self) -> impl Iterator { self.closest_peers .into_iter() @@ -364,14 +375,20 @@ pub enum QueryTarget { impl QueryTarget { /// Creates the corresponding RPC request to send to remote. + #[inline] pub fn to_rpc_request(&self, user_data: TUserData) -> KademliaHandlerIn { + self.clone().into_rpc_request(user_data) + } + + /// Creates the corresponding RPC request to send to remote. + pub fn into_rpc_request(self, user_data: TUserData) -> KademliaHandlerIn { match self { - QueryTarget::FindPeer(peer) => KademliaHandlerIn::FindNodeReq { - key: peer.clone(), + QueryTarget::FindPeer(key) => KademliaHandlerIn::FindNodeReq { + key, user_data, }, QueryTarget::GetProviders(key) => KademliaHandlerIn::GetProvidersReq { - key: key.clone(), + key, user_data, }, } From ac7ac455ca3d3ab22a888e6d6481d77bbc1cfd1e Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 27 Nov 2018 12:22:39 +0100 Subject: [PATCH 06/11] Query timeout test --- protocols/kad/src/query.rs | 58 +++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index c24f9e247ed..9a8f2ed3ee6 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -421,7 +421,7 @@ mod tests { use super::{QueryConfig, QueryState, QueryStatePollOut, QueryTarget}; use futures::{self, prelude::*}; use libp2p_core::PeerId; - use std::{iter, time::Duration, sync::Arc, sync::Mutex}; + use std::{iter, time::Duration, sync::Arc, sync::Mutex, thread}; use tokio; #[test] @@ -491,4 +491,60 @@ mod tests { } })); } + + #[test] + fn timeout_works() { + let random_id = PeerId::random(); + + let query = Arc::new(Mutex::new(QueryState::new(QueryConfig { + target: QueryTarget::FindPeer(PeerId::random()), + known_closest_peers: iter::once(random_id.clone()), + parallelism: 3, + num_results: 100, + rpc_timeout: Duration::from_millis(100), + }))); + + // Let's do a first polling round to obtain the `SendRpc` request. + tokio::run(futures::future::poll_fn({ + let random_id = random_id.clone(); + let query = query.clone(); + move || { + match try_ready!(Ok(query.lock().unwrap().poll())) { + QueryStatePollOut::SendRpc { peer_id, .. } if peer_id == &random_id => { + Ok(Async::Ready(())) + } + _ => panic!(), + } + } + })); + + // Wait for a bit. + thread::sleep(Duration::from_millis(200)); + + // Second polling round to check the timeout. + tokio::run(futures::future::poll_fn({ + let query = query.clone(); + move || { + match try_ready!(Ok(query.lock().unwrap().poll())) { + QueryStatePollOut::CancelRpc { peer_id, .. } if peer_id == &random_id => { + Ok(Async::Ready(())) + } + _ => panic!(), + } + } + })); + + // Third polling round for finished. + tokio::run(futures::future::poll_fn({ + let query = query.clone(); + move || { + match try_ready!(Ok(query.lock().unwrap().poll())) { + QueryStatePollOut::Finished => { + Ok(Async::Ready(())) + } + _ => panic!(), + } + } + })); + } } From 9e98b58b10d027bcca670c26bd162c701448f4ef Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 27 Nov 2018 14:33:52 +0100 Subject: [PATCH 07/11] Work on topology --- core/src/topology/mod.rs | 37 ++++++++++++++++++++++------------ protocols/kad/src/behaviour.rs | 2 +- protocols/kad/src/topology.rs | 8 ++++---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/core/src/topology/mod.rs b/core/src/topology/mod.rs index 04e7a22a1ec..f6cebb822fb 100644 --- a/core/src/topology/mod.rs +++ b/core/src/topology/mod.rs @@ -23,13 +23,8 @@ use {Multiaddr, PeerId}; /// Storage for the network topology. pub trait Topology { - /// Adds a discovered address to the topology. - fn add_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr); /// Returns the addresses to try use to reach the given peer. fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec; - /// Returns all the known peers. - // TODO: bad API ; also, is the method even a good idea? - fn peers(&self) -> Vec; } /// Topology of the network stored in memory. @@ -45,6 +40,30 @@ impl MemoryTopology { list: Default::default() } } + + /// Returns true if the topology is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.list.is_empty() + } + + /// Adds an address to the topology. + #[inline] + pub fn add_address(&mut self, peer: PeerId, addr: Multiaddr) { + self.list.entry(peer).or_insert_with(|| Vec::new()).push(addr); + } + + /// Returns a list of all the known peers in the topology. + #[inline] + pub fn peers(&self) -> impl Iterator { + self.list.keys() + } + + /// Returns an iterator to all the entries in the topology. + #[inline] + pub fn iter(&self) -> impl Iterator { + self.list.iter().flat_map(|(p, l)| l.iter().map(move |ma| (p, ma))) + } } impl Default for MemoryTopology { @@ -55,15 +74,7 @@ impl Default for MemoryTopology { } impl Topology for MemoryTopology { - fn add_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr) { - self.list.entry(peer.clone()).or_insert_with(|| Vec::new()).push(addr); - } - fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { self.list.get(peer).map(|v| v.clone()).unwrap_or(Vec::new()) } - - fn peers(&self) -> Vec { - self.list.keys().map(|k| k.clone()).collect() - } } diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 24e103d25e7..6827f085d2e 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -365,7 +365,7 @@ where > { // Flush the changes to the topology that we want to make. for (peer_id, addr, connection_ty) in self.add_to_topology.drain() { - topology.add_kad_discovered_address(&peer_id, addr, connection_ty); + topology.add_kad_discovered_address(peer_id, addr, connection_ty); } self.add_to_topology.shrink_to_fit(); for (key, provider) in self.add_provider.drain() { diff --git a/protocols/kad/src/topology.rs b/protocols/kad/src/topology.rs index 94dc16536ed..25706ff267a 100644 --- a/protocols/kad/src/topology.rs +++ b/protocols/kad/src/topology.rs @@ -33,7 +33,7 @@ pub trait KademliaTopology: Topology { type GetProvidersIter: Iterator; /// Adds an address discovered through Kademlia to the topology. - fn add_kad_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr, + fn add_kad_discovered_address(&mut self, peer: PeerId, addr: Multiaddr, connection_ty: KadConnectionType); /// Returns the known peers closest by XOR distance to the `target`. @@ -53,12 +53,12 @@ impl KademliaTopology for MemoryTopology { type ClosestPeersIter = vec::IntoIter; type GetProvidersIter = vec::IntoIter; - fn add_kad_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr, _: KadConnectionType) { - Topology::add_discovered_address(self, peer, addr) + fn add_kad_discovered_address(&mut self, peer: PeerId, addr: Multiaddr, _: KadConnectionType) { + self.add_address(peer, addr) } fn closest_peers(&mut self, target: &Multihash, _: usize) -> Self::ClosestPeersIter { - let mut list = self.peers(); + let mut list = self.peers().map(|p| p.clone()).collect::>(); list.sort_by(|a, b| target.distance_with(b.as_ref()).cmp(&target.distance_with(a.as_ref()))); list.into_iter() } From c640a68832190b21fe3e3b92c3a6b88cb6a0738a Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 28 Nov 2018 14:29:38 +0100 Subject: [PATCH 08/11] More work --- protocols/kad/src/behaviour.rs | 112 +++++++++++++++++---------------- protocols/kad/src/protocol.rs | 3 +- protocols/kad/src/topology.rs | 10 ++- 3 files changed, 68 insertions(+), 57 deletions(-) diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 6827f085d2e..248d8f93a37 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use fnv::{FnvHashMap, FnvHashSet}; -use futures::prelude::*; +use futures::{prelude::*, stream}; use handler::{KademliaHandler, KademliaHandlerEvent, KademliaHandlerIn, KademliaRequestId}; use libp2p_core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction}; use libp2p_core::{protocols_handler::ProtocolsHandler, topology::Topology, Multiaddr, PeerId}; @@ -28,7 +28,7 @@ use protocol::{KadConnectionType, KadPeer}; use query::{QueryConfig, QueryState, QueryStatePollOut, QueryTarget}; use rand; use smallvec::SmallVec; -use std::{cmp::Ordering, marker::PhantomData, time::Duration}; +use std::{cmp::Ordering, marker::PhantomData, time::Duration, time::Instant}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Interval; use topology::KademliaTopology; @@ -38,8 +38,9 @@ pub struct Kademlia { /// Peer ID of the local node. local_peer_id: PeerId, - /// All the iterative queries we are currently performing, with their ID. - active_queries: FnvHashMap, + /// All the iterative queries we are currently performing, with their ID. The last parameter + /// is the list of accumulated providers for `GET_PROVIDERS` queries. + active_queries: FnvHashMap)>, /// List of queries to start once we are inside `poll()`. queries_to_starts: SmallVec<[(QueryId, QueryTarget, QueryPurpose); 8]>, @@ -55,22 +56,22 @@ pub struct Kademlia { next_query_id: QueryId, /// Requests received by a remote that we should fulfill as soon as possible. - remote_requests: SmallVec<[(PeerId, KademliaRequestId, RemoteRequestToFulfill); 4]>, + remote_requests: SmallVec<[(PeerId, KademliaRequestId, QueryTarget); 4]>, /// List of multihashes that we're providing. /// - /// Note that we use a `PeerId` so that we know that is uses SHA-256. The question as to how to + /// Note that we use a `PeerId` so that we know that it uses SHA-256. The question as to how to /// handle more hashes should eventually be resolved. providing_keys: SmallVec<[PeerId; 8]>, /// Interval to send `ADD_PROVIDER` messages to everyone. - refresh_add_providers: Interval, + refresh_add_providers: stream::Fuse, - /// `α` in the Kademlia reference documentation. Designates the maximum number of queries we + /// `α` in the Kademlia reference papers. Designates the maximum number of queries that we /// perform in parallel. parallelism: usize, - /// `k` in the Kademlia reference documentation. Number of results in a find node query. + /// `k` in the Kademlia reference papers. Number of results in a find node query. num_results: usize, /// Timeout for each individual RPC query. @@ -79,16 +80,20 @@ pub struct Kademlia { /// Events to return when polling. queued_events: SmallVec<[NetworkBehaviourAction, KademliaOut>; 32]>, - /// List of addresses to add to the topology. + /// List of addresses to add to the topology as soon as we are in `poll()`. add_to_topology: SmallVec<[(PeerId, Multiaddr, KadConnectionType); 32]>, - /// List of providers to add to the topology. + /// List of providers to add to the topology as soon as we are in `poll()`. add_provider: SmallVec<[(Multihash, PeerId); 32]>, /// Marker to pin the generics. marker: PhantomData, } +/// Opaque type. Each query that we start gets a unique number. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct QueryId(usize); + /// Reason why we have this query in the list of queries. #[derive(Debug, Clone, PartialEq, Eq)] enum QueryPurpose { @@ -130,7 +135,7 @@ impl Kademlia { next_query_id: QueryId(0), remote_requests: SmallVec::new(), providing_keys: SmallVec::new(), - refresh_add_providers: Interval::new_interval(Duration::from_secs(60)), // TODO: constant + refresh_add_providers: Interval::new_interval(Duration::from_secs(60)).fuse(), // TODO: constant parallelism, num_results: 20, rpc_timeout: Duration::from_secs(8), @@ -159,21 +164,21 @@ impl Kademlia { fn build_local_kad_peer(&self) -> KadPeer { KadPeer { node_id: self.local_peer_id.clone(), - multiaddrs: Vec::new(), // TODO: + multiaddrs: Vec::new(), // FIXME: return the addresses we're listening on connection_ty: KadConnectionType::Connected, } } /// Builds the answer to a request. - fn build_result(&self, query: RemoteRequestToFulfill, request_id: KademliaRequestId, topology: &mut TTopology) + fn build_result(&self, query: QueryTarget, request_id: KademliaRequestId, topology: &mut TTopology) -> KademliaHandlerIn where TTopology: KademliaTopology { match query { - RemoteRequestToFulfill::FindNode(key) => { + QueryTarget::FindPeer(key) => { let closer_peers = topology .closest_peers(key.as_ref(), self.num_results) - .map(|peer_id| build_kad_peer(topology, peer_id)) + .map(|peer_id| build_kad_peer(peer_id, topology, &self.connected_peers)) .collect(); KademliaHandlerIn::FindNodeRes { @@ -181,17 +186,17 @@ impl Kademlia { request_id, } }, - RemoteRequestToFulfill::GetProviders(key) => { + QueryTarget::GetProviders(key) => { let closer_peers = topology .closest_peers(&key, self.num_results) - .map(|peer_id| build_kad_peer(topology, peer_id)) + .map(|peer_id| build_kad_peer(peer_id, topology, &self.connected_peers)) .collect(); let local_node_is_providing = self.providing_keys.iter().any(|k| k.as_ref() == &key); let provider_peers = topology .get_providers(&key) - .map(|peer_id| build_kad_peer(topology, peer_id)) + .map(|peer_id| build_kad_peer(peer_id, topology, &self.connected_peers)) .chain(if local_node_is_providing { Some(self.build_local_kad_peer()) } else { @@ -237,18 +242,19 @@ impl Kademlia { if !self.providing_keys.iter().any(|k| k == &key) { self.providing_keys.push(key); } - unimplemented!() // TODO: + + // Trigger the next refresh now. + self.refresh_add_providers = Interval::new(Instant::now(), Duration::from_secs(60)).fuse(); } /// Cancels a registration done with `add_providing`. /// /// There doesn't exist any "remove provider" message to broadcast on the network, therefore we - /// will still be registered as a provider in the DHT for as long as the timeout didn't expire. + /// will still be registered as a provider in the DHT for as long as the timeout doesn't expire. pub fn remove_providing(&mut self, key: &Multihash) { if let Some(position) = self.providing_keys.iter().position(|k| k.as_ref() == key) { self.providing_keys.remove(position); } - unimplemented!() // TODO: } /// Internal function that starts a query. @@ -287,7 +293,7 @@ where let was_in = self.connected_peers.remove(id); debug_assert!(was_in); - for (query, _) in self.active_queries.values_mut() { + for (query, _, _) in self.active_queries.values_mut() { query.inject_rpc_error(id); } } @@ -295,7 +301,7 @@ where fn inject_node_event(&mut self, source: PeerId, event: KademliaHandlerEvent) { match event { KademliaHandlerEvent::FindNodeReq { key, request_id } => { - self.remote_requests.push((source, request_id, RemoteRequestToFulfill::FindNode(key))); + self.remote_requests.push((source, request_id, QueryTarget::FindPeer(key))); return; } KademliaHandlerEvent::FindNodeRes { @@ -310,12 +316,12 @@ where .push((peer.node_id.clone(), addr.clone(), peer.connection_ty)); } } - if let Some((query, _)) = self.active_queries.get_mut(&user_data) { + if let Some((query, _, _)) = self.active_queries.get_mut(&user_data) { query.inject_rpc_result(&source, closer_peers.into_iter().map(|kp| kp.node_id)) } } KademliaHandlerEvent::GetProvidersReq { key, request_id } => { - self.remote_requests.push((source, request_id, RemoteRequestToFulfill::GetProviders(key))); + self.remote_requests.push((source, request_id, QueryTarget::GetProviders(key))); return; } KademliaHandlerEvent::GetProvidersRes { @@ -323,23 +329,25 @@ where provider_peers, user_data, } => { - // TODO: provider peers? how does that work? - // It is possible that we obtain a response for a query that has finished, which is - // why we may not find an entry in `self.active_queries`. - for peer in closer_peers.iter() { + for peer in closer_peers.iter().chain(provider_peers.iter()) { for addr in peer.multiaddrs.iter() { self.add_to_topology .push((peer.node_id.clone(), addr.clone(), peer.connection_ty)); } } - if let Some((query, _)) = self.active_queries.get_mut(&user_data) { + // It is possible that we obtain a response for a query that has finished, which is + // why we may not find an entry in `self.active_queries`. + if let Some((query, _, providers)) = self.active_queries.get_mut(&user_data) { + for peer in provider_peers { + providers.push(peer.node_id); + } query.inject_rpc_result(&source, closer_peers.into_iter().map(|kp| kp.node_id)) } } - KademliaHandlerEvent::QueryError { error, user_data } => { + KademliaHandlerEvent::QueryError { user_data, .. } => { // It is possible that we obtain a response for a query that has finished, which is // why we may not find an entry in `self.active_queries`. - if let Some((query, _)) = self.active_queries.get_mut(&user_data) { + if let Some((query, _, _)) = self.active_queries.get_mut(&user_data) { query.inject_rpc_error(&source) } } @@ -382,7 +390,8 @@ where self.start_query(QueryTarget::FindPeer(provided), purpose); } }, - Ok(Async::Ready(None)) | Err(_) => panic!(), // TODO: + // Ignore errors. + Ok(Async::Ready(None)) | Err(_) => {}, } // Start queries that are waiting to start. @@ -400,7 +409,8 @@ where known_closest_peers, }), query_purpose, - ), + Vec::new() // TODO: insert ourselves if we provide the data? + ) ); } self.queries_to_starts.shrink_to_fit(); @@ -425,7 +435,7 @@ where // If iterating finds a query that is finished, stores it here and stops looping. let mut finished_query = None; - 'queries_iter: for (&query_id, (query, _)) in self.active_queries.iter_mut() { + 'queries_iter: for (&query_id, (query, _, _)) in self.active_queries.iter_mut() { loop { match query.poll() { Async::Ready(QueryStatePollOut::Finished) => { @@ -459,7 +469,7 @@ where } if let Some(finished_query) = finished_query { - let (query, purpose) = self + let (query, purpose, provider_peers) = self .active_queries .remove(&finished_query) .expect("finished_query was gathered when iterating active_queries ; qed"); @@ -468,6 +478,7 @@ where QueryPurpose::UserRequest => { let event = match query.target().clone() { QueryTarget::FindPeer(key) => { + debug_assert!(provider_peers.is_empty()); KademliaOut::FindNodeResult { key, closer_peers: query.into_closest_peers().collect(), @@ -477,7 +488,7 @@ where KademliaOut::GetProvidersResult { key, closer_peers: query.into_closest_peers().collect(), - provider_peers: Vec::new(), // FIXME: + provider_peers, } }, }; @@ -527,20 +538,6 @@ pub enum KademliaOut { }, } -/// Opaque type. Each query that we start gets a unique number. -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct QueryId(usize); - -/// Request received by a remote that we should fulfill as soon as possible. -#[derive(Debug, Clone, PartialEq, Eq)] -enum RemoteRequestToFulfill { - /// `FIND_NODE` request. - FindNode(PeerId), - /// `GET_PROVIDERS` request. - GetProviders(Multihash), - // TODO: `FIND_VALUE` -} - // Generates a random `PeerId` that belongs to the given bucket. // // Returns an error if `bucket_num` is out of range. @@ -577,14 +574,21 @@ fn gen_random_id(my_id: &PeerId, bucket_num: usize) -> Result { /// Builds a `KadPeer` struct corresponding to the given `PeerId`. /// /// > **Note**: This is just a convenience function that doesn't do anything note-worthy. -fn build_kad_peer(topology: &mut TTopology, peer_id: PeerId) -> KadPeer +fn build_kad_peer(peer_id: PeerId, topology: &mut TTopology, connected_peers: &FnvHashSet) -> KadPeer where TTopology: Topology { let multiaddrs = topology.addresses_of_peer(&peer_id); + // TODO: implement the other possibilities correctly + let connection_ty = if connected_peers.contains(&peer_id) { + KadConnectionType::Connected + } else { + KadConnectionType::NotConnected + }; + KadPeer { node_id: peer_id, multiaddrs, - connection_ty: KadConnectionType::NotConnected, // TODO: + connection_ty, } } diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index d1976870879..e1b9d5f4952 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -160,7 +160,8 @@ where .from_err::() .with::<_, fn(_) -> _, _>(|response| -> Result<_, IoError> { let proto_struct = resp_msg_to_proto(response); - Ok(proto_struct.write_to_bytes().unwrap()) // TODO: error? + proto_struct.write_to_bytes() + .map_err(|err| IoError::new(IoErrorKind::InvalidData, err.to_string())) }) .and_then:: _, _>(|bytes: BytesMut| { let request = protobuf::parse_from_bytes(&bytes)?; diff --git a/protocols/kad/src/topology.rs b/protocols/kad/src/topology.rs index 25706ff267a..7070ab8881d 100644 --- a/protocols/kad/src/topology.rs +++ b/protocols/kad/src/topology.rs @@ -38,13 +38,19 @@ pub trait KademliaTopology: Topology { /// Returns the known peers closest by XOR distance to the `target`. /// - /// The `max` parameter is the maximum number of results that we are going to use. + /// The `max` parameter is the maximum number of results that we are going to use. If more + /// than `max` elements are returned, they will be ignored. fn closest_peers(&mut self, target: &Multihash, max: usize) -> Self::ClosestPeersIter; /// Registers the given peer as provider of the resource with the given ID. + /// + /// > **Note**: There is no `remove_provider` method. Implementations must include a + /// > time-to-live system so that entries disappear after a while. + // TODO: specify the TTL? it has to match the timeout in the behaviour somehow, but this could + // also be handled by the user fn add_provider(&mut self, key: Multihash, peer_id: PeerId); - /// Returns the list of providers that have been registered. + /// Returns the list of providers that have been registered with `add_provider`. fn get_providers(&mut self, key: &Multihash) -> Self::GetProvidersIter; } From c042638391e9863cf547f6342b7c4ca16116ad9c Mon Sep 17 00:00:00 2001 From: Toralf Wittner Date: Thu, 29 Nov 2018 11:27:49 +0100 Subject: [PATCH 09/11] Update protocols/kad/src/topology.rs Co-Authored-By: tomaka --- protocols/kad/src/topology.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/kad/src/topology.rs b/protocols/kad/src/topology.rs index 7070ab8881d..ad6ce7d37b5 100644 --- a/protocols/kad/src/topology.rs +++ b/protocols/kad/src/topology.rs @@ -64,7 +64,7 @@ impl KademliaTopology for MemoryTopology { } fn closest_peers(&mut self, target: &Multihash, _: usize) -> Self::ClosestPeersIter { - let mut list = self.peers().map(|p| p.clone()).collect::>(); + let mut list = self.peers().cloned().collect::>(); list.sort_by(|a, b| target.distance_with(b.as_ref()).cmp(&target.distance_with(a.as_ref()))); list.into_iter() } From 8a5b1ae3a70255af2dc79e7408e5b2a8e4c534c8 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Thu, 29 Nov 2018 11:29:41 +0100 Subject: [PATCH 10/11] Fix trailing whitespaces --- examples/ipfs-kad.rs | 2 +- protocols/kad/src/protocol.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/ipfs-kad.rs b/examples/ipfs-kad.rs index 4e3b386c73d..18a89ca192e 100644 --- a/examples/ipfs-kad.rs +++ b/examples/ipfs-kad.rs @@ -50,7 +50,7 @@ fn main() { let upgrade = mplex::MplexConfig::new().map_outbound(move |muxer| (peer_id, muxer) ); upgrade::apply_outbound(out.stream, upgrade).map_err(|e| e.into_io_error()) }); - + // Create the topology of the network with the IPFS bootstrap nodes. let mut topology = libp2p::core::topology::MemoryTopology::empty(); topology.add_address("QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ".parse().unwrap(), "/ip4/104.131.131.82/tcp/4001".parse().unwrap()); diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index e1b9d5f4952..ba34727083b 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -472,7 +472,7 @@ mod tests { fn correct_transfer() { // We open a server and a client, send a message between the two, and check that they were // successfully received. - + test_one(KadMsg::Ping); test_one(KadMsg::FindNodeReq { key: PeerId::random(), @@ -508,19 +508,19 @@ mod tests { }, }); // TODO: all messages - + fn test_one(msg_server: KadMsg) { let msg_client = msg_server.clone(); let (tx, rx) = mpsc::channel(); - + let bg_thread = thread::spawn(move || { let transport = TcpConfig::new().with_upgrade(KademliaProtocolConfig); - + let (listener, addr) = transport .listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()) .unwrap(); tx.send(addr).unwrap(); - + let future = listener .into_future() .map_err(|(err, _)| err) @@ -533,9 +533,9 @@ mod tests { let mut rt = Runtime::new().unwrap(); let _ = rt.block_on(future).unwrap(); }); - + let transport = TcpConfig::new().with_upgrade(KademliaProtocolConfig); - + let future = transport .dial(rx.recv().unwrap()) .unwrap_or_else(|_| panic!()) From b8eec864e2b0fc75af167cfe1f01d75fc9b32074 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Thu, 29 Nov 2018 11:30:44 +0100 Subject: [PATCH 11/11] Use if let --- protocols/kad/src/query.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index 9a8f2ed3ee6..8035f791fa8 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -144,9 +144,8 @@ impl QueryState { // Mark the peer as succeeded. for (peer_id, state) in self.closest_peers.iter_mut() { if peer_id == result_source { - match state { - state @ QueryPeerState::InProgress(_) => *state = QueryPeerState::Succeeded, - _ => (), + if let state @ QueryPeerState::InProgress(_) = state { + *state = QueryPeerState::Succeeded; } } }