diff --git a/protocols/kad/dht.proto b/protocols/kad/dht.proto index 79599eac98f2..4ff2f6e265b5 100644 --- a/protocols/kad/dht.proto +++ b/protocols/kad/dht.proto @@ -18,6 +18,14 @@ message Record { // Time the record was received, set by receiver string timeReceived = 5; + + // The original publisher of the record. + // Currently specific to rust-libp2p. + bytes publisher = 666; + + // The remaining TTL of the record, in seconds. + // Currently specific to rust-libp2p. + uint32 ttl = 777; }; message Message { diff --git a/protocols/kad/src/addresses.rs b/protocols/kad/src/addresses.rs index 3a1d6d57719d..0c1aa65d986b 100644 --- a/protocols/kad/src/addresses.rs +++ b/protocols/kad/src/addresses.rs @@ -22,7 +22,7 @@ use libp2p_core::Multiaddr; use smallvec::SmallVec; use std::fmt; -/// List of addresses of a peer. +/// A non-empty list of (unique) addresses of a peer in the routing table. #[derive(Clone)] pub struct Addresses { addrs: SmallVec<[Multiaddr; 6]>, @@ -30,59 +30,69 @@ pub struct Addresses { impl Addresses { /// Creates a new list of addresses. - pub fn new() -> Addresses { - Addresses { - addrs: SmallVec::new(), - } + pub fn new(addr: Multiaddr) -> Addresses { + let mut addrs = SmallVec::new(); + addrs.push(addr); + Addresses { addrs } + } + + /// Gets a reference to the first address in the list. + pub fn first(&self) -> &Multiaddr { + &self.addrs[0] } - /// Returns an iterator over the list of addresses. + /// Returns an iterator over the addresses. pub fn iter(&self) -> impl Iterator { self.addrs.iter() } + /// Returns the number of addresses in the list. + pub fn len(&self) -> usize { + self.addrs.len() + } + /// Converts the addresses into a `Vec`. pub fn into_vec(self) -> Vec { self.addrs.into_vec() } - /// Returns true if the list of addresses is empty. - pub fn is_empty(&self) -> bool { - self.addrs.is_empty() - } + /// Removes the given address from the list. + /// + /// Returns true if the address was found and removed, false otherwise. + /// The last remaining address in the list cannot be remvoved. + /// + /// An address should only be removed if is determined to be invalid or + /// otherwise unreachable. + pub fn remove(&mut self, addr: &Multiaddr) -> bool { + if self.addrs.len() == 1 { + return false + } - /// Removes the given address from the list. Typically called if an address is determined to - /// be invalid or unreachable. - pub fn remove(&mut self, addr: &Multiaddr) { if let Some(pos) = self.addrs.iter().position(|a| a == addr) { self.addrs.remove(pos); + if self.addrs.len() <= self.addrs.inline_size() { + self.addrs.shrink_to_fit(); + } + return true } - if self.addrs.len() <= self.addrs.inline_size() { - self.addrs.shrink_to_fit(); - } - } - - /// Clears the list. It is empty afterwards. - pub fn clear(&mut self) { - self.addrs.clear(); - self.addrs.shrink_to_fit(); + false } - /// Inserts an address in the list. No effect if the address was already in the list. - pub fn insert(&mut self, addr: Multiaddr) { + /// Adds a new address to the end of the list. + /// + /// Returns true if the address was added, false otherwise (i.e. if the + /// address is already in the list). + pub fn insert(&mut self, addr: Multiaddr) -> bool { if self.addrs.iter().all(|a| *a != addr) { self.addrs.push(addr); + true + } else { + false } } } -impl Default for Addresses { - fn default() -> Self { - Addresses::new() - } -} - impl fmt::Debug for Addresses { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_list() diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index 9f1b425a562c..7a073f75726a 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -18,30 +18,34 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{KBUCKET_PENDING_TIMEOUT, ADD_PROVIDER_INTERVAL}; +//! Implementation of the `Kademlia` network behaviour. + +mod test; + use crate::addresses::Addresses; -use crate::handler::{KademliaHandler, KademliaHandlerEvent, KademliaHandlerIn}; +use crate::handler::{KademliaHandler, KademliaRequestId, KademliaHandlerEvent, KademliaHandlerIn}; +use crate::jobs::*; use crate::kbucket::{self, KBucketsTable, NodeStatus}; use crate::protocol::{KadConnectionType, KadPeer}; use crate::query::{Query, QueryId, QueryPool, QueryConfig, QueryPoolState}; -use crate::record::{MemoryRecordStorage, RecordStore, Record, RecordStorageError}; +use crate::record::{store::{self, RecordStore}, Record, ProviderRecord}; use fnv::{FnvHashMap, FnvHashSet}; -use futures::{prelude::*, stream}; +use futures::prelude::*; use libp2p_core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters}; use libp2p_core::{protocols_handler::ProtocolsHandler, Multiaddr, PeerId}; +use log::{info, debug}; use multihash::Multihash; use smallvec::SmallVec; -use std::{borrow::Cow, error, marker::PhantomData, time::Duration, num::NonZeroU8}; +use std::{borrow::Cow, error, iter, marker::PhantomData, time::Duration}; use std::collections::VecDeque; +use std::num::NonZeroUsize; use tokio_io::{AsyncRead, AsyncWrite}; -use wasm_timer::{Instant, Interval}; - -mod test; +use wasm_timer::Instant; /// Network behaviour that handles Kademlia. -pub struct Kademlia { +pub struct Kademlia { /// The Kademlia routing table. - kbuckets: KBucketsTable, + kbuckets: KBucketsTable, Addresses>, /// An optional protocol name override to segregate DHTs in the network. protocol_name_override: Option>, @@ -49,27 +53,28 @@ pub struct Kademlia { /// The currently active (i.e. in-progress) queries. queries: QueryPool, - /// List of peers known to be connected. + /// The currently connected peers. + /// + /// This is a superset of the connected peers currently in the routing table. connected_peers: FnvHashSet, /// A list of pending request to peers that are not currently connected. /// These requests are sent as soon as a connection to the peer is established. pending_rpcs: SmallVec<[(PeerId, KademliaHandlerIn); 8]>, - /// List of values and peers that are providing them. - /// - /// Our local peer ID can be in this container. - values_providers: FnvHashMap>, + /// Periodic job for re-publication of provider records for keys + /// provided by the local node. + add_provider_job: Option, - /// List of values that we are providing ourselves. Must be kept in sync with - /// `values_providers`. - providing_keys: FnvHashSet, + /// Periodic job for (re-)replication and (re-)publishing of + /// regular (value-)records. + put_record_job: Option, - /// List of providers to add to the topology as soon as we are in `poll()`. - add_provider: SmallVec<[(Multihash, PeerId); 32]>, + /// The TTL of regular (value-)records. + record_ttl: Option, - /// Interval to send `ADD_PROVIDER` messages to everyone. - refresh_add_providers: stream::Fuse, + /// The TTL of provider records. + provider_record_ttl: Option, /// Queued events to return when the behaviour is being polled. queued_events: VecDeque, KademliaEvent>>, @@ -78,30 +83,40 @@ pub struct Kademlia { marker: PhantomData, /// The record storage. - records: TStore, + store: TStore, } /// The configuration for the `Kademlia` behaviour. /// /// The configuration is consumed by [`Kademlia::new`]. -pub struct KademliaConfig { - local_peer_id: PeerId, - protocol_name_override: Option>, +#[derive(Debug, Clone)] +pub struct KademliaConfig { + kbucket_pending_timeout: Duration, query_config: QueryConfig, - records: Option + protocol_name_override: Option>, + record_ttl: Option, + record_replication_interval: Option, + record_publication_interval: Option, + provider_record_ttl: Option, + provider_publication_interval: Option, } -impl KademliaConfig { - /// Creates a new KademliaConfig for the given local peer ID. - pub fn new(local_peer_id: PeerId) -> Self { +impl Default for KademliaConfig { + fn default() -> Self { KademliaConfig { - local_peer_id, - protocol_name_override: None, + kbucket_pending_timeout: Duration::from_secs(60), query_config: QueryConfig::default(), - records: None + protocol_name_override: None, + record_ttl: Some(Duration::from_secs(36 * 60 * 60)), + record_replication_interval: Some(Duration::from_secs(60 * 60)), + record_publication_interval: Some(Duration::from_secs(24 * 60 * 60)), + provider_publication_interval: Some(Duration::from_secs(12 * 60 * 60)), + provider_record_ttl: Some(Duration::from_secs(24 * 60 * 60)), } } +} +impl KademliaConfig { /// Sets a custom protocol name. /// /// Kademlia nodes only communicate with other nodes using the same protocol name. Using a @@ -111,12 +126,6 @@ impl KademliaConfig { self } - /// Sets the record storage implementation to use. - pub fn set_storage(&mut self, store: TStore) -> &mut Self { - self.records = Some(store); - self - } - /// Sets the timeout for a single query. /// /// > **Note**: A single query usually comprises at least as many requests @@ -130,76 +139,196 @@ impl KademliaConfig { /// Sets the replication factor to use. /// - /// The default replication factor is [`K_VALUE`]. - pub fn set_replication_factor(&mut self, replication_factor: usize) -> &mut Self { + /// The replication factor determines to how many closest peers + /// a record is replicated. The default is [`K_VALUE`]. + pub fn set_replication_factor(&mut self, replication_factor: NonZeroUsize) -> &mut Self { self.query_config.replication_factor = replication_factor; self } + + /// Sets the TTL for stored records. + /// + /// The TTL should be significantly longer than the (re-)publication + /// interval, to avoid premature expiration of records. The default is 36 hours. + /// + /// `None` means records never expire. + /// + /// Does not apply to provider records. + pub fn set_record_ttl(&mut self, record_ttl: Option) -> &mut Self { + self.record_ttl = record_ttl; + self + } + + /// Sets the (re-)replication interval for stored records. + /// + /// Periodic replication of stored records ensures that the records + /// are always replicated to the available nodes closest to the key in the + /// context of DHT topology changes (i.e. nodes joining and leaving), thus + /// ensuring persistence until the record expires. Replication does not + /// prolong the regular lifetime of a record (for otherwise it would live + /// forever regardless of the configured TTL). The expiry of a record + /// is only extended through re-publication. + /// + /// This interval should be significantly shorter than the publication + /// interval, to ensure persistence between re-publications. The default + /// is 1 hour. + /// + /// `None` means that stored records are never re-replicated. + /// + /// Does not apply to provider records. + pub fn set_replication_interval(&mut self, interval: Option) -> &mut Self { + self.record_replication_interval = interval; + self + } + + /// Sets the (re-)publication interval of stored records. + /// + /// Records persist in the DHT until they expire. By default, published records + /// are re-published in regular intervals for as long as the record exists + /// in the local storage of the original publisher, thereby extending the + /// records lifetime. + /// + /// This interval should be significantly shorter than the record TTL, to + /// ensure records do not expire prematurely. The default is 24 hours. + /// + /// `None` means that stored records are never automatically re-published. + /// + /// Does not apply to provider records. + pub fn set_publication_interval(&mut self, interval: Option) -> &mut Self { + self.record_publication_interval = interval; + self + } + + /// Sets the TTL for provider records. + /// + /// `None` means that stored provider records never expire. + /// + /// Must be significantly larger than the provider publication interval. + pub fn set_provider_record_ttl(&mut self, ttl: Option) -> &mut Self { + self.provider_record_ttl = ttl; + self + } + + /// Sets the interval at which provider records for keys provided + /// by the local node are re-published. + /// + /// `None` means that stored provider records are never automatically re-published. + /// + /// Must be significantly less than the provider record TTL. + pub fn set_provider_publication_interval(&mut self, interval: Option) -> &mut Self { + self.provider_publication_interval = interval; + self + } } impl Kademlia where - TStore: RecordStore + for<'a> TStore: RecordStore<'a> { /// Creates a new `Kademlia` network behaviour with the given configuration. - pub fn new(config: KademliaConfig) -> Self - where - TStore: Default - { - let local_key = kbucket::Key::new(config.local_peer_id); - let pending_rpcs = SmallVec::with_capacity(config.query_config.replication_factor); + pub fn new(id: PeerId, store: TStore) -> Self { + Self::with_config(id, store, Default::default()) + } + + /// Creates a new `Kademlia` network behaviour with the given configuration. + pub fn with_config(id: PeerId, store: TStore, config: KademliaConfig) -> Self { + let local_key = kbucket::Key::new(id.clone()); + let pending_rpcs = SmallVec::with_capacity(config.query_config.replication_factor.get()); + + let put_record_job = config + .record_replication_interval + .or(config.record_publication_interval) + .map(|interval| PutRecordJob::new( + id.clone(), + interval, + config.record_publication_interval, + config.record_ttl, + )); + + let add_provider_job = config + .provider_publication_interval + .map(AddProviderJob::new); + Kademlia { - kbuckets: KBucketsTable::new(local_key, KBUCKET_PENDING_TIMEOUT), + store, + kbuckets: KBucketsTable::new(local_key, config.kbucket_pending_timeout), protocol_name_override: config.protocol_name_override, - queued_events: VecDeque::with_capacity(config.query_config.replication_factor), + queued_events: VecDeque::with_capacity(config.query_config.replication_factor.get()), queries: QueryPool::new(config.query_config), connected_peers: Default::default(), pending_rpcs, - values_providers: FnvHashMap::default(), - providing_keys: FnvHashSet::default(), - refresh_add_providers: Interval::new_interval(ADD_PROVIDER_INTERVAL).fuse(), - add_provider: SmallVec::new(), - records: config.records.unwrap_or_default(), + add_provider_job, + put_record_job, + record_ttl: config.record_ttl, + provider_record_ttl: config.provider_record_ttl, marker: PhantomData, } } - /// Adds a known address of a peer participating in the Kademlia DHT to the + /// Adds a known listen address of a peer participating in the DHT to the /// routing table. /// - /// This allows prepopulating the Kademlia routing table with known addresses, - /// e.g. for bootstrap nodes. - pub fn add_address(&mut self, peer_id: &PeerId, address: Multiaddr) { - let key = kbucket::Key::new(peer_id.clone()); + /// Explicitly adding addresses of peers serves two purposes: + /// + /// 1. In order for a node to join the DHT, it must know about at least + /// one other node of the DHT. + /// + /// 2. When a remote peer initiates a connection and that peer is not + /// yet in the routing table, the `Kademlia` behaviour must be + /// informed of an address on which that peer is listening for + /// connections before it can be added to the routing table + /// from where it can subsequently be discovered by all peers + /// in the DHT. + /// + /// If the routing table has been updated as a result of this operation, + /// a [`KademliaEvent::RoutingUpdated`] event is emitted. + pub fn add_address(&mut self, peer: &PeerId, address: Multiaddr) { + let key = kbucket::Key::new(peer.clone()); match self.kbuckets.entry(&key) { kbucket::Entry::Present(mut entry, _) => { - entry.value().insert(address); + if entry.value().insert(address) { + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::RoutingUpdated { + peer: peer.clone(), + addresses: entry.value().clone(), + old_peer: None, + } + )) + } } kbucket::Entry::Pending(mut entry, _) => { entry.value().insert(address); } kbucket::Entry::Absent(entry) => { - let mut addresses = Addresses::new(); - addresses.insert(address); - match entry.insert(addresses, NodeStatus::Disconnected) { + let addresses = Addresses::new(address); + let status = + if self.connected_peers.contains(peer) { + NodeStatus::Connected + } else { + NodeStatus::Disconnected + }; + match entry.insert(addresses.clone(), status) { kbucket::InsertResult::Inserted => { - let event = KademliaEvent::RoutingUpdated { - new_peer: peer_id.clone(), - old_peer: None, - }; - self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::RoutingUpdated { + peer: peer.clone(), + addresses, + old_peer: None, + } + )); + }, + kbucket::InsertResult::Full => { + debug!("Bucket full. Peer not added to routing table: {}", peer) }, - kbucket::InsertResult::Full => (), kbucket::InsertResult::Pending { disconnected } => { self.queued_events.push_back(NetworkBehaviourAction::DialPeer { peer_id: disconnected.into_preimage(), }) }, } - return; }, - kbucket::Entry::SelfEntry => return, - }; + kbucket::Entry::SelfEntry => {}, + } } /// Returns an iterator over all peer IDs of nodes currently contained in a bucket @@ -228,20 +357,24 @@ where /// The result of this operation is delivered in [`KademliaEvent::GetRecordResult`]. pub fn get_record(&mut self, key: &Multihash, quorum: Quorum) { let quorum = quorum.eval(self.queries.config().replication_factor); - let mut records = Vec::with_capacity(quorum); - - if let Some(record) = self.records.get(key) { - records.push(record.into_owned()); - if quorum == 1 { - self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( - KademliaEvent::GetRecordResult(Ok(GetRecordOk { records })) - )); - return; + let mut records = Vec::with_capacity(quorum.get()); + + if let Some(record) = self.store.get(key) { + if record.is_expired(Instant::now()) { + self.store.remove(key) + } else { + records.push(record.into_owned()); + if quorum.get() == 1 { + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::GetRecordResult(Ok(GetRecordOk { records })) + )); + return; + } } } let target = kbucket::Key::from(key.clone()); - let info = QueryInfo::GetRecord { key: key.clone(), records, quorum }; + let info = QueryInfo::GetRecord { key: key.clone(), records, quorum, cache_at: None }; let peers = self.kbuckets.closest_keys(&target); let inner = QueryInner::new(info); self.queries.add_iter_closest(target.clone(), peers, inner); @@ -251,28 +384,56 @@ where /// /// The result of this operation is delivered in [`KademliaEvent::PutRecordResult`]. /// - /// The record is always stored locally. - pub fn put_record(&mut self, record: Record, quorum: Quorum) { - let quorum = quorum.eval(self.queries.config().replication_factor); - if let Err(error) = self.records.put(record.clone()) { + /// The record is always stored locally with the given expiration. If the record's + /// expiration is `None`, the common case, it does not expire in local storage + /// but is still replicated with the configured record TTL. To remove the record + /// locally and stop it from being re-published in the DHT, see [`Kademlia::remove_record`]. + /// + /// After the initial publication of the record, it is subject to (re-)replication + /// and (re-)publication as per the configured intervals. Periodic (re-)publication + /// does not update the record's expiration in local storage, thus a given record + /// with an explicit expiration will always expire at that instant and until then + /// is subject to regular (re-)replication and (re-)publication. + pub fn put_record(&mut self, mut record: Record, quorum: Quorum) { + record.publisher = Some(self.kbuckets.local_key().preimage().clone()); + if let Err(err) = self.store.put(record.clone()) { self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( KademliaEvent::PutRecordResult(Err( - PutRecordError::LocalStorageError(error) + PutRecordError::LocalStorageError { + key: record.key, + cause: err + } )) )); } else { + record.expires = record.expires.or_else(|| + self.record_ttl.map(|ttl| Instant::now() + ttl)); + let quorum = quorum.eval(self.queries.config().replication_factor); let target = kbucket::Key::from(record.key.clone()); let peers = self.kbuckets.closest_keys(&target); - let info = QueryInfo::PreparePutRecord { - key: record.key, - value: record.value, - quorum - }; + let info = QueryInfo::PreparePutRecord { record, quorum }; let inner = QueryInner::new(info); self.queries.add_iter_closest(target.clone(), peers, inner); } } + /// Removes the record with the given key from _local_ storage, + /// if the local node is the publisher of the record. + /// + /// Has no effect if a record for the given key is stored locally but + /// the local node is not a publisher of the record. + /// + /// This is a _local_ operation. However, it also has the effect that + /// the record will no longer be periodically re-published, allowing the + /// record to eventually expire throughout the DHT. + pub fn remove_record(&mut self, key: &Multihash) { + if let Some(r) = self.store.get(key) { + if r.publisher.as_ref() == Some(self.kbuckets.local_key().preimage()) { + self.store.remove(key) + } + } + } + /// Bootstraps the local node to join the DHT. /// /// Bootstrapping is a multi-step operation that starts with a lookup of the local node's @@ -283,44 +444,54 @@ where /// refreshed by initiating an additional bootstrapping query for each such /// bucket with random keys. /// - /// The results of this operation are delivered in [`KademliaEvent::BootstrapResult`], - /// with one event per query. + /// The result(s) of this operation are delivered in [`KademliaEvent::BootstrapResult`], + /// with one event per bootstrapping query. /// /// > **Note**: Bootstrapping requires at least one node of the DHT to be known. /// > See [`Kademlia::add_address`]. pub fn bootstrap(&mut self) { let local_key = self.kbuckets.local_key().clone(); - let info = QueryInfo::Bootstrap { target: local_key.preimage().clone() }; + let info = QueryInfo::Bootstrap { peer: local_key.preimage().clone() }; let peers = self.kbuckets.closest_keys(&local_key).collect::>(); // TODO: Emit error if `peers` is empty? BootstrapError::NoPeers? let inner = QueryInner::new(info); self.queries.add_iter_closest(local_key, peers, inner); } - /// Registers the local node as the provider of a value for the given key. + /// Establishes the local node as a provider of a value for the given key. + /// + /// This operation publishes a provider record with the given key and + /// identity of the local node to the peers closest to the key, thus establishing + /// the local node as a provider. /// - /// This operation will start periodically sending `ADD_PROVIDER` messages to the nodes - /// closest to the key, so that other nodes can find this node as a result of - /// a `GET_PROVIDERS` iterative request on the DHT. + /// The publication of the provider records is periodically repeated as per the + /// configured interval, to renew the expiry and account for changes to the DHT + /// topology. A provider record may be removed from local storage and + /// thus no longer re-published by calling [`Kademlia::stop_providing`]. /// /// In contrast to the standard Kademlia push-based model for content distribution /// implemented by [`Kademlia::put_record`], the provider API implements a - /// pull-based model that may be used in addition, or as an alternative to, - /// the push-based model. The means by which the actual value is obtained - /// from a provider is out of scope of the libp2p Kademlia provider API. + /// pull-based model that may be used in addition or as an alternative. + /// The means by which the actual value is obtained from a provider is out of scope + /// of the libp2p Kademlia provider API. /// - /// The periodic results of the provider announcements sent by this node are delivered - /// in [`KademliaEvent::AddProviderResult`]. + /// The results of the (repeated) provider announcements sent by this node are + /// delivered in [`KademliaEvent::AddProviderResult`]. pub fn start_providing(&mut self, key: Multihash) { - self.providing_keys.insert(key.clone()); - let providers = self.values_providers.entry(key).or_insert_with(Default::default); - let local_id = self.kbuckets.local_key().preimage(); - if !providers.iter().any(|peer_id| peer_id == local_id) { - providers.push(local_id.clone()); + let record = ProviderRecord::new(key.clone(), self.kbuckets.local_key().preimage().clone()); + if let Err(err) = self.store.add_provider(record) { + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::AddProviderResult(Err( + AddProviderError::LocalStorageError(err) + )) + )); + } else { + let target = kbucket::Key::from(key.clone()); + let peers = self.kbuckets.closest_keys(&target); + let info = QueryInfo::PrepareAddProvider { key }; + let inner = QueryInner::new(info); + self.queries.add_iter_closest(target.clone(), peers, inner); } - - // Trigger the next refresh now. - self.refresh_add_providers = Interval::new(Instant::now(), ADD_PROVIDER_INTERVAL).fuse(); } /// Stops the local node from announcing that it is a provider for the given key. @@ -328,17 +499,7 @@ where /// This is a local operation. The local node will still be considered as a /// provider for the key by other nodes until these provider records expire. pub fn stop_providing(&mut self, key: &Multihash) { - self.providing_keys.remove(key); - - let providers = match self.values_providers.get_mut(key) { - Some(p) => p, - None => return, - }; - - if let Some(position) = providers.iter().position(|k| k == key) { - providers.remove(position); - providers.shrink_to_fit(); - } + self.store.remove_provider(key, self.kbuckets.local_key().preimage()); } /// Performs a lookup for providers of a value to the given key. @@ -346,7 +507,7 @@ where /// The result of this operation is delivered in [`KademliaEvent::GetProvidersResult`]. pub fn get_providers(&mut self, key: Multihash) { let info = QueryInfo::GetProviders { - target: key.clone(), + key: key.clone(), providers: Vec::new(), }; let target = kbucket::Key::from(key); @@ -355,10 +516,10 @@ where self.queries.add_iter_closest(target.clone(), peers, inner); } - /// Processes discovered peers from an iterative `Query`. + /// Processes discovered peers from a successful request in an iterative `Query`. fn discovered<'a, I>(&'a mut self, query_id: &QueryId, source: &PeerId, peers: I) where - I: Iterator + Clone + I: Iterator + Clone { let local_id = self.kbuckets.local_key().preimage().clone(); let others_iter = peers.filter(|p| p.node_id != local_id); @@ -375,7 +536,7 @@ where if let Some(query) = self.queries.get_mut(query_id) { for peer in others_iter.clone() { - query.inner.untrusted_addresses + query.inner.addresses .insert(peer.node_id.clone(), peer.multiaddrs.iter().cloned().collect()); } query.on_success(source, others_iter.cloned().map(|kp| kp.node_id)) @@ -392,7 +553,7 @@ where self.kbuckets .closest(target) .filter(|e| e.node.key.preimage() != source) - .take(self.queries.config().replication_factor) + .take(self.queries.config().replication_factor.get()) .map(KadPeer::from) .collect() } @@ -401,27 +562,53 @@ where /// Collects all peers who are known to be providers of the value for a given `Multihash`. fn provider_peers(&mut self, key: &Multihash, source: &PeerId) -> Vec { let kbuckets = &mut self.kbuckets; - self.values_providers - .get(key) + self.store.providers(key) .into_iter() - .flat_map(|peers| peers) .filter_map(move |p| - if p != source { - let key = kbucket::Key::new(p.clone()); + if &p.provider != source { + let key = kbucket::Key::new(p.provider.clone()); kbuckets.entry(&key).view().map(|e| KadPeer::from(e.to_owned())) } else { None }) + .take(self.queries.config().replication_factor.get()) .collect() } + /// Starts an iterative `ADD_PROVIDER` query for the given key. + fn start_add_provider(&mut self, key: Multihash) { + let info = QueryInfo::PrepareAddProvider { key: key.clone() }; + let target = kbucket::Key::from(key); + let peers = self.kbuckets.closest_keys(&target); + let inner = QueryInner::new(info); + self.queries.add_iter_closest(target.clone(), peers, inner); + } + + /// Starts an iterative `PUT_VALUE` query for the given record. + fn start_put_record(&mut self, record: Record, quorum: Quorum) { + let quorum = quorum.eval(self.queries.config().replication_factor); + let target = kbucket::Key::from(record.key.clone()); + let peers = self.kbuckets.closest_keys(&target); + let info = QueryInfo::PreparePutRecord { record, quorum }; + let inner = QueryInner::new(info); + self.queries.add_iter_closest(target.clone(), peers, inner); + } + /// Updates the connection status of a peer in the Kademlia routing table. fn connection_updated(&mut self, peer: PeerId, address: Option, new_status: NodeStatus) { let key = kbucket::Key::new(peer.clone()); match self.kbuckets.entry(&key) { kbucket::Entry::Present(mut entry, old_status) => { if let Some(address) = address { - entry.value().insert(address); + if entry.value().insert(address) { + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::RoutingUpdated { + peer, + addresses: entry.value().clone(), + old_peer: None, + } + )) + } } if old_status != new_status { entry.update(new_status); @@ -437,26 +624,36 @@ where } }, - kbucket::Entry::Absent(entry) => if new_status == NodeStatus::Connected { - let mut addresses = Addresses::new(); - if let Some(address) = address { - addresses.insert(address); - } - match entry.insert(addresses, new_status) { - kbucket::InsertResult::Inserted => { - let event = KademliaEvent::RoutingUpdated { - new_peer: peer.clone(), - old_peer: None, - }; - self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent(event)); - }, - kbucket::InsertResult::Full => (), - kbucket::InsertResult::Pending { disconnected } => { - debug_assert!(!self.connected_peers.contains(disconnected.preimage())); - self.queued_events.push_back(NetworkBehaviourAction::DialPeer { - peer_id: disconnected.into_preimage(), - }) - }, + kbucket::Entry::Absent(entry) => { + // Only connected nodes with a known address are newly inserted. + if new_status == NodeStatus::Connected { + if let Some(address) = address { + let addresses = Addresses::new(address); + match entry.insert(addresses.clone(), new_status) { + kbucket::InsertResult::Inserted => { + let event = KademliaEvent::RoutingUpdated { + peer: peer.clone(), + addresses, + old_peer: None, + }; + self.queued_events.push_back( + NetworkBehaviourAction::GenerateEvent(event)); + }, + kbucket::InsertResult::Full => { + debug!("Bucket full. Peer not added to routing table: {}", peer) + }, + kbucket::InsertResult::Pending { disconnected } => { + debug_assert!(!self.connected_peers.contains(disconnected.preimage())); + self.queued_events.push_back(NetworkBehaviourAction::DialPeer { + peer_id: disconnected.into_preimage(), + }) + }, + } + } else { + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::UnroutablePeer { peer } + )); + } } }, _ => {} @@ -469,9 +666,9 @@ where { let result = q.into_result(); match result.inner.info { - QueryInfo::Bootstrap { target } => { + QueryInfo::Bootstrap { peer } => { let local_key = self.kbuckets.local_key().clone(); - if &target == local_key.preimage() { + if &peer == local_key.preimage() { // The lookup for the local key finished. To complete the bootstrap process, // a bucket refresh should be performed for every bucket farther away than // the first non-empty bucket (which are most likely no more than the last @@ -505,13 +702,13 @@ where }).collect::>(); for target in targets { - let info = QueryInfo::Bootstrap { target: target.clone().into_preimage() }; + let info = QueryInfo::Bootstrap { peer: target.clone().into_preimage() }; let peers = self.kbuckets.closest_keys(&target); let inner = QueryInner::new(info); self.queries.add_iter_closest(target.clone(), peers, inner); } } - Some(KademliaEvent::BootstrapResult(Ok(BootstrapOk { peer: target }))) + Some(KademliaEvent::BootstrapResult(Ok(BootstrapOk { peer }))) } QueryInfo::GetClosestPeers { key, .. } => { @@ -520,22 +717,22 @@ where ))) } - QueryInfo::GetProviders { target, providers } => { + QueryInfo::GetProviders { key, providers } => { Some(KademliaEvent::GetProvidersResult(Ok( GetProvidersOk { - key: target, + key, providers, closest_peers: result.peers.collect() } ))) } - QueryInfo::PrepareAddProvider { target } => { + QueryInfo::PrepareAddProvider { key } => { let closest_peers = result.peers.map(kbucket::Key::from); let provider_id = params.local_peer_id().clone(); let external_addresses = params.external_addresses().collect(); let inner = QueryInner::new(QueryInfo::AddProvider { - target, + key, provider_id, external_addresses }); @@ -543,14 +740,23 @@ where None } - QueryInfo::AddProvider { target, .. } => { + QueryInfo::AddProvider { key, .. } => { Some(KademliaEvent::AddProviderResult(Ok( - AddProviderOk { key: target } + AddProviderOk { key } ))) } - QueryInfo::GetRecord { key, records, quorum, .. } => { - let result = if records.len() >= quorum { + QueryInfo::GetRecord { key, records, quorum, cache_at } => { + let result = if records.len() >= quorum.get() { // [not empty] + if let Some(cache_key) = cache_at { + // Cache the record at the closest node to the key that + // did not return the record. + let record = records.first().expect("[not empty]").clone(); + let quorum = NonZeroUsize::new(1).expect("1 > 0"); + let info = QueryInfo::PutRecord { record, quorum, num_results: 0 }; + let inner = QueryInner::new(info); + self.queries.add_fixed(iter::once(cache_key), inner); + } Ok(GetRecordOk { records }) } else if records.is_empty() { Err(GetRecordError::NotFound { @@ -563,19 +769,20 @@ where Some(KademliaEvent::GetRecordResult(result)) } - QueryInfo::PreparePutRecord { key, value, quorum } => { + QueryInfo::PreparePutRecord { record, quorum } => { let closest_peers = result.peers.map(kbucket::Key::from); - let info = QueryInfo::PutRecord { key, value, num_results: 0, quorum }; + let info = QueryInfo::PutRecord { record, quorum, num_results: 0 }; let inner = QueryInner::new(info); self.queries.add_fixed(closest_peers, inner); None } - QueryInfo::PutRecord { key, num_results, quorum, .. } => { - let result = if num_results >= quorum { + QueryInfo::PutRecord { record, quorum, num_results, .. } => { + let key = record.key; + let result = if num_results >= quorum.get() { Ok(PutRecordOk { key }) } else { - Err(PutRecordError::QuorumFailed { key, num_results, quorum }) + Err(PutRecordError::QuorumFailed { key, quorum, num_results }) }; Some(KademliaEvent::PutRecordResult(result)) } @@ -586,17 +793,17 @@ where fn query_timeout(&self, query: Query) -> KademliaEvent { let result = query.into_result(); match result.inner.info { - QueryInfo::Bootstrap { target } => + QueryInfo::Bootstrap { peer } => KademliaEvent::BootstrapResult(Err( - BootstrapError::Timeout { peer: target })), + BootstrapError::Timeout { peer })), - QueryInfo::PrepareAddProvider { target } => + QueryInfo::PrepareAddProvider { key } => KademliaEvent::AddProviderResult(Err( - AddProviderError::Timeout { key: target })), + AddProviderError::Timeout { key })), - QueryInfo::AddProvider { target, .. } => + QueryInfo::AddProvider { key, .. } => KademliaEvent::AddProviderResult(Err( - AddProviderError::Timeout { key: target })), + AddProviderError::Timeout { key })), QueryInfo::GetClosestPeers { key } => KademliaEvent::GetClosestPeersResult(Err( @@ -605,33 +812,101 @@ where peers: result.peers.collect() })), - QueryInfo::PreparePutRecord { key, quorum, .. } => + QueryInfo::PreparePutRecord { record, quorum, .. } => KademliaEvent::PutRecordResult(Err( - PutRecordError::Timeout { key, num_results: 0, quorum })), + PutRecordError::Timeout { key: record.key, num_results: 0, quorum })), - QueryInfo::PutRecord { key, num_results, quorum, .. } => + QueryInfo::PutRecord { record, quorum, num_results, .. } => KademliaEvent::PutRecordResult(Err( - PutRecordError::Timeout { key, num_results, quorum })), + PutRecordError::Timeout { key: record.key, num_results, quorum })), - QueryInfo::GetRecord { key, records, quorum } => + QueryInfo::GetRecord { key, records, quorum, .. } => KademliaEvent::GetRecordResult(Err( GetRecordError::Timeout { key, records, quorum })), - QueryInfo::GetProviders { target, providers } => + QueryInfo::GetProviders { key, providers } => KademliaEvent::GetProvidersResult(Err( GetProvidersError::Timeout { - key: target, + key, providers, closest_peers: result.peers.collect() })), } } + + /// Processes a record received from a peer. + fn record_received(&mut self, source: PeerId, request_id: KademliaRequestId, mut record: Record) { + let now = Instant::now(); + + // Calculate the expiration exponentially inversely proportional to the + // number of nodes between the local node and the closest node to the key + // (beyond the replication factor). This ensures avoiding over-caching + // outside of the k closest nodes to a key. + let target = kbucket::Key::from(record.key.clone()); + let num_between = self.kbuckets.count_nodes_between(&target); + let k = self.queries.config().replication_factor.get(); + let num_beyond_k = (usize::max(k, num_between) - k) as u32; + let expiration = self.record_ttl.map(|ttl| + now + Duration::from_secs(ttl.as_secs() >> num_beyond_k) + ); + record.expires = record.expires.min(expiration); + + if let Some(job) = self.put_record_job.as_mut() { + // Ignore the record in the next run of the replication + // job, since we can assume the sender replicated the + // record to the k closest peers. Effectively, only + // one of the k closest peers performs a replication + // in the configured interval, assuming a shared interval. + job.skip(record.key.clone()) + } + + match self.store.put(record.clone()) { + Ok(()) => { + self.queued_events.push_back(NetworkBehaviourAction::SendEvent { + peer_id: source, + event: KademliaHandlerIn::PutRecordRes { + key: record.key.clone(), + value: record.value.clone(), + request_id, + }, + }) + } + Err(e) => { + info!("Record not stored: {:?}", e); + self.queued_events.push_back(NetworkBehaviourAction::SendEvent { + peer_id: source, + event: KademliaHandlerIn::Reset(request_id) + }) + } + } + } + + /// Processes a provider record received from a peer. + fn provider_received(&mut self, key: Multihash, provider: KadPeer) { + self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( + KademliaEvent::Discovered { + peer_id: provider.node_id.clone(), + addresses: provider.multiaddrs.clone(), + ty: provider.connection_ty, + })); + + if &provider.node_id != self.kbuckets.local_key().preimage() { + let record = ProviderRecord { + key, + provider: provider.node_id, + expires: self.provider_record_ttl.map(|ttl| Instant::now() + ttl) + }; + if let Err(e) = self.store.add_provider(record) { + info!("Provider record not stored: {:?}", e); + } + } + } } impl NetworkBehaviour for Kademlia where TSubstream: AsyncRead + AsyncWrite, - TStore: RecordStore, + for<'a> TStore: RecordStore<'a>, { type ProtocolsHandler = KademliaHandler; type OutEvent = KademliaEvent; @@ -648,41 +923,45 @@ where // We should order addresses from decreasing likelyhood of connectivity, so start with // the addresses of that peer in the k-buckets. let key = kbucket::Key::new(peer_id.clone()); - let mut out_list = + let mut peer_addrs = if let kbucket::Entry::Present(mut entry, _) = self.kbuckets.entry(&key) { - entry.value().iter().cloned().collect::>() + let addrs = entry.value().iter().cloned().collect::>(); + debug_assert!(!addrs.is_empty(), "Empty peer addresses in routing table."); + addrs } else { Vec::new() }; // We add to that a temporary list of addresses from the ongoing queries. for query in self.queries.iter() { - if let Some(addrs) = query.inner.untrusted_addresses.get(peer_id) { - for addr in addrs { - out_list.push(addr.clone()); - } + if let Some(addrs) = query.inner.addresses.get(peer_id) { + peer_addrs.extend(addrs.iter().cloned()) } } - out_list + peer_addrs } - fn inject_connected(&mut self, id: PeerId, endpoint: ConnectedPoint) { - while let Some(pos) = self.pending_rpcs.iter().position(|(p, _)| p == &id) { + fn inject_connected(&mut self, peer: PeerId, endpoint: ConnectedPoint) { + while let Some(pos) = self.pending_rpcs.iter().position(|(p, _)| p == &peer) { let (_, rpc) = self.pending_rpcs.remove(pos); self.queued_events.push_back(NetworkBehaviourAction::SendEvent { - peer_id: id.clone(), + peer_id: peer.clone(), event: rpc, }); } + // The remote's address can only be put into the routing table, + // and thus shared with other nodes, if the local node is the dialer, + // since the remote address on an inbound connection is specific to + // that connection (e.g. typically the TCP port numbers). let address = match endpoint { ConnectedPoint::Dialer { address } => Some(address), ConnectedPoint::Listener { .. } => None, }; - self.connection_updated(id.clone(), address, NodeStatus::Connected); - self.connected_peers.insert(id); + self.connection_updated(peer.clone(), address, NodeStatus::Connected); + self.connected_peers.insert(peer); } fn inject_addr_reach_failure(&mut self, peer_id: Option<&PeerId>, addr: &Multiaddr, _: &dyn error::Error) { @@ -690,13 +969,11 @@ where let key = kbucket::Key::new(peer_id.clone()); if let Some(addrs) = self.kbuckets.entry(&key).value() { - // TODO: don't remove the address if the error is that we are already connected - // to this peer addrs.remove(addr); } for query in self.queries.iter_mut() { - if let Some(addrs) = query.inner.untrusted_addresses.get_mut(&peer_id) { + if let Some(addrs) = query.inner.addresses.get_mut(&peer_id) { addrs.retain(|a| a != addr); } } @@ -730,7 +1007,6 @@ where if let Some(addrs) = self.kbuckets.entry(&kbucket::Key::new(peer_id)).value() { if let ConnectedPoint::Dialer { address } = new_endpoint { - // TODO: Remove the old address, i.e. from `_old`? addrs.insert(address); } } @@ -795,50 +1071,75 @@ where } } - KademliaHandlerEvent::AddProvider { key, provider_peer } => { - self.queued_events.push_back(NetworkBehaviourAction::GenerateEvent( - KademliaEvent::Discovered { - peer_id: provider_peer.node_id.clone(), - addresses: provider_peer.multiaddrs.clone(), - ty: provider_peer.connection_ty, - })); - // TODO: Expire provider records. - self.add_provider.push((key, provider_peer.node_id)); + KademliaHandlerEvent::AddProvider { key, provider } => { + // Only accept a provider record from a legitimate peer. + if provider.node_id != source { + return + } + + self.provider_received(key, provider) } - KademliaHandlerEvent::GetValue { key, request_id } => { - let (result, closer_peers) = match self.records.get(&key) { + KademliaHandlerEvent::GetRecord { key, request_id } => { + // Lookup the record locally. + let record = match self.store.get(&key) { Some(record) => { - (Some(record.into_owned()), Vec::new()) + if record.is_expired(Instant::now()) { + self.store.remove(&key); + None + } else { + Some(record.into_owned()) + } }, - None => { - let closer_peers = self.find_closest(&kbucket::Key::from(key), &source); - (None, closer_peers) - } + None => None }; + // If no record is found, at least report known closer peers. + let closer_peers = + if record.is_none() { + self.find_closest(&kbucket::Key::from(key), &source) + } else { + Vec::new() + }; + self.queued_events.push_back(NetworkBehaviourAction::SendEvent { peer_id: source, - event: KademliaHandlerIn::GetValueRes { - result, + event: KademliaHandlerIn::GetRecordRes { + record, closer_peers, request_id, }, }); } - KademliaHandlerEvent::GetValueRes { - result, + KademliaHandlerEvent::GetRecordRes { + record, closer_peers, user_data, } => { if let Some(query) = self.queries.get_mut(&user_data) { - if let QueryInfo::GetRecord { records, quorum, .. } = &mut query.inner.info { - if let Some(result) = result { - records.push(result); - if records.len() == *quorum { + if let QueryInfo::GetRecord { + key, records, quorum, cache_at + } = &mut query.inner.info { + if let Some(record) = record { + records.push(record); + if records.len() == quorum.get() { query.finish() } + } else if quorum.get() == 1 { + // It is a "standard" Kademlia query, for which the + // closest node to the key that did *not* return the + // value is tracked in order to cache the record on + // that node if the query turns out to be successful. + let source_key = kbucket::Key::from(source.clone()); + if let Some(cache_key) = cache_at { + let key = kbucket::Key::from(key.clone()); + if source_key.distance(&key) < cache_key.distance(&key) { + *cache_at = Some(source_key) + } + } else { + *cache_at = Some(source_key) + } } } } @@ -846,27 +1147,15 @@ where self.discovered(&user_data, &source, closer_peers.iter()); } - KademliaHandlerEvent::PutValue { - key, - value, + KademliaHandlerEvent::PutRecord { + record, request_id } => { - // TODO: Log errors and immediately reset the stream on error instead of letting the request time out. - if let Ok(()) = self.records.put(Record { key: key.clone(), value: value.clone() }) { - self.queued_events.push_back(NetworkBehaviourAction::SendEvent { - peer_id: source, - event: KademliaHandlerIn::PutValueRes { - key, - value, - request_id, - }, - }); - } + self.record_received(source, request_id, record); } - KademliaHandlerEvent::PutValueRes { - key: _, - user_data, + KademliaHandlerEvent::PutRecordRes { + user_data, .. } => { if let Some(query) = self.queries.get_mut(&user_data) { query.on_success(&source, vec![]); @@ -874,7 +1163,7 @@ where num_results, quorum, .. } = &mut query.inner.info { *num_results += 1; - if *num_results == *quorum { + if *num_results == quorum.get() { query.finish() } } @@ -891,33 +1180,34 @@ where > { let now = Instant::now(); - // Flush the changes to the topology that we want to make. - for (key, provider) in self.add_provider.drain() { - // Don't add ourselves to the providers. - if &provider == self.kbuckets.local_key().preimage() { - continue; - } - let providers = self.values_providers.entry(key).or_insert_with(Default::default); - if !providers.iter().any(|peer_id| peer_id == &provider) { - providers.push(provider); + // Calculate the available capacity for queries triggered by background jobs. + let mut jobs_query_capacity = JOBS_MAX_QUERIES - self.queries.size(); + + // Run the periodic provider announcement job. + if let Some(mut job) = self.add_provider_job.take() { + let num = usize::min(JOBS_MAX_NEW_QUERIES, jobs_query_capacity); + for _ in 0 .. num { + if let Async::Ready(r) = job.poll(&mut self.store, now) { + self.start_add_provider(r.key) + } else { + break + } } + jobs_query_capacity -= num; + self.add_provider_job = Some(job); } - self.add_provider.shrink_to_fit(); - - // Handle `refresh_add_providers`. - match self.refresh_add_providers.poll() { - Ok(Async::NotReady) => {}, - Ok(Async::Ready(Some(_))) => { - for target in self.providing_keys.clone().into_iter() { - let info = QueryInfo::PrepareAddProvider { target: target.clone() }; - let target = kbucket::Key::from(target); - let peers = self.kbuckets.closest_keys(&target); - let inner = QueryInner::new(info); - self.queries.add_iter_closest(target.clone(), peers, inner); + + // Run the periodic record replication / publication job. + if let Some(mut job) = self.put_record_job.take() { + let num = usize::min(JOBS_MAX_NEW_QUERIES, jobs_query_capacity); + for _ in 0 .. num { + if let Async::Ready(r) = job.poll(&mut self.store, now) { + self.start_put_record(r, Quorum::All) + } else { + break } - }, - // Ignore errors. - Ok(Async::Ready(None)) | Err(_) => {}, + } + self.put_record_job = Some(job); } loop { @@ -928,8 +1218,10 @@ where // Drain applied pending entries from the routing table. if let Some(entry) = self.kbuckets.take_applied_pending() { + let kbucket::Node { key, value } = entry.inserted; let event = KademliaEvent::RoutingUpdated { - new_peer: entry.inserted.into_preimage(), + peer: key.into_preimage(), + addresses: value, old_peer: entry.evicted.map(|n| n.key.into_preimage()) }; return Async::Ready(NetworkBehaviourAction::GenerateEvent(event)) @@ -949,6 +1241,14 @@ where } QueryPoolState::Waiting(Some((query, peer_id))) => { let event = query.inner.info.to_request(query.id()); + // TODO: AddProvider requests yield no response, so the query completes + // as soon as all requests have been sent. However, the handler should + // better emit an event when the request has been sent (and report + // an error if sending fails), instead of immediately reporting + // "success" somewhat prematurely here. + if let QueryInfo::AddProvider { .. } = &query.inner.info { + query.on_success(&peer_id, vec![]) + } if self.connected_peers.contains(&peer_id) { self.queued_events.push_back(NetworkBehaviourAction::SendEvent { peer_id, event @@ -964,7 +1264,7 @@ where } } - // No finished query or finished write produced an immediate event. + // No immediate event was produced as a result of a finished query. // If no new events have been queued either, signal `NotReady` to // be polled again later. if self.queued_events.is_empty() { @@ -974,8 +1274,9 @@ where } } -/// A quorum w.r.t. the configured replication factor specifies the minimum number of distinct -/// nodes that must be successfully contacted in order for a query to succeed. +/// A quorum w.r.t. the configured replication factor specifies the minimum +/// number of distinct nodes that must be successfully contacted in order +/// for a query to succeed. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Quorum { One, @@ -983,19 +1284,19 @@ pub enum Quorum { Three, Majority, All, - N(NonZeroU8) + N(NonZeroUsize) } impl Quorum { /// Evaluate the quorum w.r.t a given total (number of peers). - pub fn eval(&self, total: usize) -> usize { + pub fn eval(&self, total: NonZeroUsize) -> NonZeroUsize { match self { - Quorum::One => 1, - Quorum::Two => 2, - Quorum::Three => 3, - Quorum::Majority => total / 2 + 1, + Quorum::One => NonZeroUsize::new(1).expect("1 != 0"), + Quorum::Two => NonZeroUsize::new(2).expect("2 != 0"), + Quorum::Three => NonZeroUsize::new(3).expect("3 != 0"), + Quorum::Majority => NonZeroUsize::new(total.get() / 2 + 1).expect("n + 1 != 0"), Quorum::All => total, - Quorum::N(n) => usize::min(total, n.get() as usize) + Quorum::N(n) => NonZeroUsize::min(total, *n) } } } @@ -1006,7 +1307,7 @@ impl Quorum { /// The events produced by the `Kademlia` behaviour. /// /// See [`Kademlia::poll`]. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum KademliaEvent { /// The result of [`Kademlia::bootstrap`]. BootstrapResult(BootstrapResult), @@ -1017,34 +1318,44 @@ pub enum KademliaEvent { /// The result of [`Kademlia::get_providers`]. GetProvidersResult(GetProvidersResult), - /// The result of periodic queries initiated by [`Kademlia::start_providing`]. + /// The periodic result of [`Kademlia::start_providing`]. AddProviderResult(AddProviderResult), /// The result of [`Kademlia::get_record`]. GetRecordResult(GetRecordResult), - /// The result of [`Kademlia::put_record`]. + /// The periodic result of [`Kademlia::put_record`]. PutRecordResult(PutRecordResult), - /// A new peer in the DHT has been discovered during an iterative query. + /// A peer has been discovered during a query. Discovered { - /// The identifier of the discovered peer. + /// The ID of the discovered peer. peer_id: PeerId, /// The known addresses of the discovered peer. addresses: Vec, - /// The connection status of the reported peer, as seen by the local - /// peer. + /// The connection status reported by the discovered peer + /// towards the local peer. ty: KadConnectionType, }, - /// A peer in the DHT has been added to the routing table. + /// The routing table has been updated. RoutingUpdated { - /// The ID of the peer that was added to a bucket in the routing table. - new_peer: PeerId, + /// The ID of the peer that was added or updated. + peer: PeerId, + /// The list of known addresses of `peer`. + addresses: Addresses, /// The ID of the peer that was evicted from the routing table to make /// room for the new peer, if any. old_peer: Option, }, + + /// A peer has connected for whom no listen address is known. + /// + /// If the peer is to be added to the local node's routing table, a known + /// listen address for the peer must be provided via [`Kademlia::add_address`]. + UnroutablePeer { + peer: PeerId + } } /// The result of [`Kademlia::get_record`]. @@ -1060,8 +1371,8 @@ pub struct GetRecordOk { #[derive(Debug, Clone)] pub enum GetRecordError { NotFound { key: Multihash, closest_peers: Vec }, - QuorumFailed { key: Multihash, records: Vec, quorum: usize }, - Timeout { key: Multihash, records: Vec, quorum: usize } + QuorumFailed { key: Multihash, records: Vec, quorum: NonZeroUsize }, + Timeout { key: Multihash, records: Vec, quorum: NonZeroUsize } } /// The result of [`Kademlia::put_record`]. @@ -1074,11 +1385,22 @@ pub struct PutRecordOk { } /// The error result of [`Kademlia::put_record`]. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum PutRecordError { - QuorumFailed { key: Multihash, num_results: usize, quorum: usize }, - Timeout { key: Multihash, num_results: usize, quorum: usize }, - LocalStorageError(RecordStorageError) + QuorumFailed { + key: Multihash, + num_results: usize, + quorum: NonZeroUsize + }, + Timeout { + key: Multihash, + num_results: usize, + quorum: NonZeroUsize + }, + LocalStorageError { + key: Multihash, + cause: store::Error + } } /// The result of [`Kademlia::bootstrap`]. @@ -1109,7 +1431,10 @@ pub struct GetClosestPeersOk { /// The error result of [`Kademlia::get_closest_peers`]. #[derive(Debug, Clone)] pub enum GetClosestPeersError { - Timeout { key: Multihash, peers: Vec } + Timeout { + key: Multihash, + peers: Vec + } } /// The result of [`Kademlia::get_providers`]. @@ -1143,15 +1468,16 @@ pub struct AddProviderOk { } /// The error result of a periodic query initiated by [`Kademlia::start_providing`]. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum AddProviderError { Timeout { key: Multihash, - } + }, + LocalStorageError(store::Error) } -impl From> for KadPeer { - fn from(e: kbucket::EntryView) -> KadPeer { +impl From, Addresses>> for KadPeer { + fn from(e: kbucket::EntryView, Addresses>) -> KadPeer { KadPeer { node_id: e.node.key.into_preimage(), multiaddrs: e.node.value.into_vec(), @@ -1167,15 +1493,17 @@ impl From> for KadPeer { // Internal query state struct QueryInner { + /// The query-specific state. info: QueryInfo, - untrusted_addresses: FnvHashMap>, + /// Addresses of peers discovered during a query. + addresses: FnvHashMap>, } impl QueryInner { fn new(info: QueryInfo) -> Self { QueryInner { info, - untrusted_addresses: Default::default() + addresses: Default::default() } } } @@ -1186,7 +1514,7 @@ enum QueryInfo { /// A bootstrapping query. Bootstrap { /// The targeted peer ID. - target: PeerId, + peer: PeerId, }, /// A query to find the closest peers to a key. @@ -1194,21 +1522,21 @@ enum QueryInfo { /// A query for the providers of a key. GetProviders { - /// Target we are searching the providers of. - target: Multihash, - /// Results to return. Filled over time. + /// The key for which to search for providers. + key: Multihash, + /// The found providers. providers: Vec, }, /// A query that searches for the closest closest nodes to a key to be /// used in a subsequent `AddProvider` query. PrepareAddProvider { - target: Multihash + key: Multihash }, /// A query that advertises the local node as a provider for a key. AddProvider { - target: Multihash, + key: Multihash, provider_id: PeerId, external_addresses: Vec, }, @@ -1216,17 +1544,15 @@ enum QueryInfo { /// A query that searches for the closest closest nodes to a key to be used /// in a subsequent `PutValue` query. PreparePutRecord { - key: Multihash, - value: Vec, - quorum: usize, + record: Record, + quorum: NonZeroUsize, }, /// A query that replicates a record to other nodes. PutRecord { - key: Multihash, - value: Vec, + record: Record, + quorum: NonZeroUsize, num_results: usize, - quorum: usize, }, /// A query that searches for values for a key. @@ -1236,7 +1562,12 @@ enum QueryInfo { /// The records found. records: Vec, /// The number of records to look for. - quorum: usize, + quorum: NonZeroUsize, + /// The closest peer to `key` that did not return a record. + /// + /// When a record is found in a standard Kademlia query (quorum == 1), + /// it is cached at this peer. + cache_at: Option>, }, } @@ -1245,45 +1576,44 @@ impl QueryInfo { /// context of a query. fn to_request(&self, query_id: QueryId) -> KademliaHandlerIn { match &self { - QueryInfo::Bootstrap { target } => KademliaHandlerIn::FindNodeReq { - key: target.clone().into(), + QueryInfo::Bootstrap { peer } => KademliaHandlerIn::FindNodeReq { + key: peer.clone().into(), user_data: query_id, }, QueryInfo::GetClosestPeers { key, .. } => KademliaHandlerIn::FindNodeReq { key: key.clone(), user_data: query_id, }, - QueryInfo::GetProviders { target, .. } => KademliaHandlerIn::GetProvidersReq { - key: target.clone(), + QueryInfo::GetProviders { key, .. } => KademliaHandlerIn::GetProvidersReq { + key: key.clone(), user_data: query_id, }, - QueryInfo::PrepareAddProvider { target } => KademliaHandlerIn::FindNodeReq { - key: target.clone(), + QueryInfo::PrepareAddProvider { key } => KademliaHandlerIn::FindNodeReq { + key: key.clone(), user_data: query_id, }, QueryInfo::AddProvider { - target, + key, provider_id, external_addresses } => KademliaHandlerIn::AddProvider { - key: target.clone(), - provider_peer: crate::protocol::KadPeer { + key: key.clone(), + provider: crate::protocol::KadPeer { node_id: provider_id.clone(), multiaddrs: external_addresses.clone(), connection_ty: crate::protocol::KadConnectionType::Connected, } }, - QueryInfo::GetRecord { key, .. } => KademliaHandlerIn::GetValue { + QueryInfo::GetRecord { key, .. } => KademliaHandlerIn::GetRecord { key: key.clone(), user_data: query_id, }, - QueryInfo::PreparePutRecord { key, .. } => KademliaHandlerIn::FindNodeReq { - key: key.clone(), + QueryInfo::PreparePutRecord { record, .. } => KademliaHandlerIn::FindNodeReq { + key: record.key.clone(), user_data: query_id, }, - QueryInfo::PutRecord { key, value, .. } => KademliaHandlerIn::PutValue { - key: key.clone(), - value: value.clone(), + QueryInfo::PutRecord { record, .. } => KademliaHandlerIn::PutRecord { + record: record.clone(), user_data: query_id } } diff --git a/protocols/kad/src/behaviour/test.rs b/protocols/kad/src/behaviour/test.rs index 3d629dbf85fa..90e7e76f1a5a 100644 --- a/protocols/kad/src/behaviour/test.rs +++ b/protocols/kad/src/behaviour/test.rs @@ -22,7 +22,9 @@ use super::*; +use crate::K_VALUE; use crate::kbucket::Distance; +use crate::record::store::memory::MemoryStore; use futures::future; use libp2p_core::{ Swarm, @@ -36,18 +38,24 @@ use libp2p_core::{ }; use libp2p_secio::SecioConfig; use libp2p_yamux as yamux; +use quickcheck::*; use rand::{Rng, random, thread_rng}; -use std::{collections::HashSet, iter::FromIterator, io, num::NonZeroU8, u64}; +use std::{collections::{HashSet, HashMap}, io, num::NonZeroUsize, u64}; use tokio::runtime::current_thread; use multihash::Hash::SHA2256; type TestSwarm = Swarm< Boxed<(PeerId, StreamMuxerBox), io::Error>, - Kademlia> + Kademlia, MemoryStore> >; /// Builds swarms, each listening on a port. Does *not* connect the nodes together. fn build_nodes(num: usize) -> (u64, Vec) { + build_nodes_with_config(num, Default::default()) +} + +/// Builds swarms, each listening on a port. Does *not* connect the nodes together. +fn build_nodes_with_config(num: usize, cfg: KademliaConfig) -> (u64, Vec) { let port_base = 1 + random::() % (u64::MAX - num as u64); let mut result: Vec> = Vec::with_capacity(num); @@ -67,22 +75,27 @@ fn build_nodes(num: usize) -> (u64, Vec) { .map_err(|e| panic!("Failed to create transport: {:?}", e)) .boxed(); - let cfg = KademliaConfig::new(local_public_key.clone().into_peer_id()); - let kad = Kademlia::new(cfg); - result.push(Swarm::new(transport, kad, local_public_key.into_peer_id())); + let local_id = local_public_key.clone().into_peer_id(); + let store = MemoryStore::new(local_id.clone()); + let behaviour = Kademlia::with_config(local_id.clone(), store, cfg.clone()); + result.push(Swarm::new(transport, behaviour, local_id)); } - let mut i = 0; - for s in result.iter_mut() { - Swarm::listen_on(s, Protocol::Memory(port_base + i).into()).unwrap(); - i += 1 + for (i, s) in result.iter_mut().enumerate() { + Swarm::listen_on(s, Protocol::Memory(port_base + i as u64).into()).unwrap(); } (port_base, result) } fn build_connected_nodes(total: usize, step: usize) -> (Vec, Vec) { - let (port_base, mut swarms) = build_nodes(total); + build_connected_nodes_with_config(total, step, Default::default()) +} + +fn build_connected_nodes_with_config(total: usize, step: usize, cfg: KademliaConfig) + -> (Vec, Vec) +{ + let (port_base, mut swarms) = build_nodes_with_config(total, cfg); let swarm_ids: Vec<_> = swarms.iter().map(Swarm::local_peer_id).cloned().collect(); let mut i = 0; @@ -100,7 +113,7 @@ fn build_connected_nodes(total: usize, step: usize) -> (Vec, Vec(rng: &mut G) { + fn run(rng: &mut impl Rng) { let num_total = rng.gen_range(2, 20); let num_group = rng.gen_range(1, num_total); let (swarm_ids, mut swarms) = build_connected_nodes(num_total, num_group); @@ -149,7 +162,7 @@ fn query_iter() { .collect() } - fn run(rng: &mut G) { + fn run(rng: &mut impl Rng) { let num_total = rng.gen_range(2, 20); let (swarm_ids, mut swarms) = build_connected_nodes(num_total, 1); @@ -241,10 +254,7 @@ fn unresponsive_not_returned_indirect() { // Add fake addresses to first. let first_peer_id = Swarm::local_peer_id(&swarms[0]).clone(); for _ in 0 .. 10 { - swarms[0].add_address( - &PeerId::random(), - multiaddr![Udp(10u16)] - ); + swarms[0].add_address(&PeerId::random(), multiaddr![Udp(10u16)]); } // Connect second to first. @@ -314,35 +324,50 @@ fn get_record_not_found() { } #[test] -fn put_value() { - fn run(rng: &mut G) { - let num_total = rng.gen_range(21, 40); - let num_group = rng.gen_range(1, usize::min(num_total, kbucket::K_VALUE)); - let (swarm_ids, mut swarms) = build_connected_nodes(num_total, num_group); - - let key = multihash::encode(SHA2256, &vec![1,2,3]).unwrap(); - let bucket_key = kbucket::Key::from(key.clone()); - - let mut sorted_peer_ids: Vec<_> = swarm_ids - .iter() - .map(|id| (id.clone(), kbucket::Key::from(id.clone()).distance(&bucket_key))) - .collect(); - - sorted_peer_ids.sort_by(|(_, d1), (_, d2)| d1.cmp(d2)); +fn put_record() { + fn prop(replication_factor: usize, records: Vec) { + let replication_factor = NonZeroUsize::new(replication_factor % (K_VALUE / 2) + 1).unwrap(); + let num_total = replication_factor.get() * 2; + let num_group = replication_factor.get(); + + let mut config = KademliaConfig::default(); + config.set_replication_factor(replication_factor); + let (swarm_ids, mut swarms) = build_connected_nodes_with_config(num_total, num_group, config); + + let records = records.into_iter() + .map(|mut r| { + // We don't want records to expire prematurely, as they would + // be removed from storage and no longer replicated, but we still + // want to check that an explicitly set expiration is preserved. + r.expires = r.expires.map(|t| t + Duration::from_secs(60)); + (r.key.clone(), r) + }) + .collect::>(); - let closest = HashSet::from_iter(sorted_peer_ids.into_iter().map(|(id, _)| id)); + for r in records.values() { + swarms[0].put_record(r.clone(), Quorum::All); + } - let record = Record { key: key.clone(), value: vec![4,5,6] }; - swarms[0].put_record(record, Quorum::All); + // Each test run republishes all records once. + let mut republished = false; + // The accumulated results for one round of publishing. + let mut results = Vec::new(); current_thread::run( - future::poll_fn(move || { - let mut check_results = false; + future::poll_fn(move || loop { + // Poll all swarms until they are "NotReady". for swarm in &mut swarms { loop { match swarm.poll().unwrap() { - Async::Ready(Some(KademliaEvent::PutRecordResult(Ok(_)))) => { - check_results = true; + Async::Ready(Some(KademliaEvent::PutRecordResult(res))) => { + match res { + Err(e) => panic!(e), + Ok(ok) => { + assert!(records.contains_key(&ok.key)); + let record = swarm.store.get(&ok.key).unwrap(); + results.push(record.into_owned()); + } + } } Async::Ready(_) => (), Async::NotReady => break, @@ -350,31 +375,64 @@ fn put_value() { } } - if check_results { - let mut have: HashSet<_> = Default::default(); - - for (i, swarm) in swarms.iter().skip(1).enumerate() { - if swarm.records.get(&key).is_some() { - have.insert(swarm_ids[i].clone()); - } - } + // All swarms are NotReady and not enough results have been collected + // so far, thus wait to be polled again for further progress. + if results.len() != records.len() { + return Ok(Async::NotReady) + } - let intersection: HashSet<_> = have.intersection(&closest).collect(); + // Consume the results, checking that each record was replicated + // correctly to the closest peers to the key. + while let Some(r) = results.pop() { + let expected = records.get(&r.key).unwrap(); + + assert_eq!(r.key, expected.key); + assert_eq!(r.value, expected.value); + assert_eq!(r.expires, expected.expires); + assert_eq!(r.publisher.as_ref(), Some(&swarm_ids[0])); + + let key = kbucket::Key::new(r.key.clone()); + let mut expected = swarm_ids.clone().split_off(1); + expected.sort_by(|id1, id2| + kbucket::Key::new(id1).distance(&key).cmp( + &kbucket::Key::new(id2).distance(&key))); + + let expected = expected + .into_iter() + .take(replication_factor.get()) + .collect::>(); + + let actual = swarms.iter().enumerate().skip(1) + .filter_map(|(i, s)| + if s.store.get(key.preimage()).is_some() { + Some(swarm_ids[i].clone()) + } else { + None + }) + .collect::>(); - assert_eq!(have.len(), kbucket::K_VALUE); - assert_eq!(intersection.len(), kbucket::K_VALUE); + assert_eq!(actual.len(), replication_factor.get()); + assert_eq!(actual, expected); + } + if republished { + assert_eq!(swarms[0].store.records().count(), records.len()); + for k in records.keys() { + swarms[0].store.remove(&k); + } + assert_eq!(swarms[0].store.records().count(), 0); + // All records have been republished, thus the test is complete. return Ok(Async::Ready(())); } - Ok(Async::NotReady) - })) + // Tell the replication job to republish asap. + swarms[0].put_record_job.as_mut().unwrap().asap(true); + republished = true; + }) + ) } - let mut rng = thread_rng(); - for _ in 0 .. 10 { - run(&mut rng); - } + QuickCheck::new().tests(3).quickcheck(prop as fn(_,_)) } #[test] @@ -386,12 +444,9 @@ fn get_value() { swarms[0].add_address(&swarm_ids[1], Protocol::Memory(port_base + 1).into()); swarms[1].add_address(&swarm_ids[2], Protocol::Memory(port_base + 2).into()); - let record = Record { - key: multihash::encode(SHA2256, &vec![1,2,3]).unwrap(), - value: vec![4,5,6] - }; + let record = Record::new(multihash::encode(SHA2256, &vec![1,2,3]).unwrap(), vec![4,5,6]); - swarms[1].records.put(record.clone()).unwrap(); + swarms[1].store.put(record.clone()).unwrap(); swarms[0].get_record(&record.key, Quorum::One); current_thread::run( @@ -415,23 +470,19 @@ fn get_value() { } #[test] -fn get_value_multiple() { - // Check that if we have responses from multiple peers, a correct number of - // results is returned. +fn get_value_many() { + // TODO: Randomise let num_nodes = 12; - let (_swarm_ids, mut swarms) = build_connected_nodes(num_nodes, num_nodes); + let (_, mut swarms) = build_connected_nodes(num_nodes, num_nodes); let num_results = 10; - let record = Record { - key: multihash::encode(SHA2256, &vec![1,2,3]).unwrap(), - value: vec![4,5,6], - }; + let record = Record::new(multihash::encode(SHA2256, &vec![1,2,3]).unwrap(), vec![4,5,6]); for i in 0 .. num_nodes { - swarms[i].records.put(record.clone()).unwrap(); + swarms[i].store.put(record.clone()).unwrap(); } - let quorum = Quorum::N(NonZeroU8::new(num_results as u8).unwrap()); + let quorum = Quorum::N(NonZeroUsize::new(num_results).unwrap()); swarms[0].get_record(&record.key, quorum); current_thread::run( @@ -452,3 +503,122 @@ fn get_value_multiple() { Ok(Async::NotReady) })) } + +#[test] +fn add_provider() { + fn prop(replication_factor: usize, keys: Vec>) { + let replication_factor = NonZeroUsize::new(replication_factor % (K_VALUE / 2) + 1).unwrap(); + let num_total = replication_factor.get() * 2; + let num_group = replication_factor.get(); + + let mut config = KademliaConfig::default(); + config.set_replication_factor(replication_factor); + + let (swarm_ids, mut swarms) = build_connected_nodes_with_config(num_total, num_group, config); + + let keys: HashSet<_> = keys.into_iter().collect(); + + // Each test run publishes all records twice. + let mut published = false; + let mut republished = false; + // The accumulated results for one round of publishing. + let mut results = Vec::new(); + + // Initiate the first round of publishing. + for k in &keys { + swarms[0].start_providing(k.preimage().clone()); + } + + current_thread::run( + future::poll_fn(move || loop { + // Poll all swarms until they are "NotReady". + for swarm in &mut swarms { + loop { + match swarm.poll().unwrap() { + Async::Ready(Some(KademliaEvent::AddProviderResult(res))) => { + match res { + Err(e) => panic!(e), + Ok(ok) => { + let key = kbucket::Key::new(ok.key.clone()); + assert!(keys.contains(&key)); + results.push(key); + } + } + } + Async::Ready(_) => (), + Async::NotReady => break, + } + } + } + + if results.len() == keys.len() { + // All requests have been sent for one round of publishing. + published = true + } + + if !published { + // Still waiting for all requests to be sent for one round + // of publishing. + return Ok(Async::NotReady) + } + + // A round of publishing is complete. Consume the results, checking that + // each key was published to the `replication_factor` closest peers. + while let Some(key) = results.pop() { + // Collect the nodes that have a provider record for `key`. + let actual = swarms.iter().enumerate().skip(1) + .filter_map(|(i, s)| + if s.store.providers(key.preimage()).len() == 1 { + Some(swarm_ids[i].clone()) + } else { + None + }) + .collect::>(); + + if actual.len() != replication_factor.get() { + // Still waiting for some nodes to process the request. + results.push(key); + return Ok(Async::NotReady) + } + + let mut expected = swarm_ids.clone().split_off(1); + expected.sort_by(|id1, id2| + kbucket::Key::new(id1).distance(&key).cmp( + &kbucket::Key::new(id2).distance(&key))); + + let expected = expected + .into_iter() + .take(replication_factor.get()) + .collect::>(); + + assert_eq!(actual, expected); + } + + // One round of publishing is complete. + assert!(results.is_empty()); + for s in &swarms { + assert_eq!(s.queries.size(), 0); + } + + if republished { + assert_eq!(swarms[0].store.provided().count(), keys.len()); + for k in &keys { + swarms[0].stop_providing(k.preimage()); + } + assert_eq!(swarms[0].store.provided().count(), 0); + // All records have been republished, thus the test is complete. + return Ok(Async::Ready(())); + } + + // Initiate the second round of publishing by telling the + // periodic provider job to run asap. + swarms[0].add_provider_job.as_mut().unwrap().asap(); + published = false; + republished = true; + }) + ) + } + + QuickCheck::new().tests(3).quickcheck(prop as fn(_,_)) +} + diff --git a/protocols/kad/src/handler.rs b/protocols/kad/src/handler.rs index fbc0bf242c0d..96a053237aac 100644 --- a/protocols/kad/src/handler.rs +++ b/protocols/kad/src/handler.rs @@ -176,26 +176,26 @@ pub enum KademliaHandlerEvent { user_data: TUserData, }, - /// The remote indicates that this list of providers is known for this key. + /// The peer announced itself as a provider of a key. AddProvider { - /// Key for which we should add providers. + /// The key for which the peer is a provider of the associated value. key: Multihash, - /// Known provider for this key. - provider_peer: KadPeer, + /// The peer that is the provider of the value for `key`. + provider: KadPeer, }, /// Request to get a value from the dht records - GetValue { + GetRecord { /// Key for which we should look in the dht key: Multihash, /// Identifier of the request. Needs to be passed back when answering. request_id: KademliaRequestId, }, - /// Response to a `KademliaHandlerIn::GetValue`. - GetValueRes { + /// Response to a `KademliaHandlerIn::GetRecord`. + GetRecordRes { /// The result is present if the key has been found - result: Option, + record: Option, /// Nodes closest to the key. closer_peers: Vec, /// The user data passed to the `GetValue`. @@ -203,20 +203,19 @@ pub enum KademliaHandlerEvent { }, /// Request to put a value in the dht records - PutValue { - /// The key of the record - key: Multihash, - /// The value of the record - value: Vec, + PutRecord { + record: Record, /// Identifier of the request. Needs to be passed back when answering. request_id: KademliaRequestId, }, - /// Response to a request to put a value - PutValueRes { - /// The key we were putting in + /// Response to a request to store a record. + PutRecordRes { + /// The key of the stored record. key: Multihash, - /// The user data passed to the `GetValue`. + /// The value of the stored record. + value: Vec, + /// The user data passed to the `PutValue`. user_data: TUserData, } } @@ -268,6 +267,8 @@ impl From> for KademliaHandlerQueryErr { /// Event to send to the handler. #[derive(Debug)] pub enum KademliaHandlerIn { + Reset(KademliaRequestId), + /// 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 { @@ -316,21 +317,21 @@ pub enum KademliaHandlerIn { /// Key for which we should add providers. key: Multihash, /// Known provider for this key. - provider_peer: KadPeer, + provider: KadPeer, }, - /// Request to get a node from the dht - GetValue { - /// The key of the value we are looking for + /// Request to retrieve a record from the DHT. + GetRecord { + /// The key of the record. key: Multihash, /// Custom data. Passed back in the out event when the results arrive. user_data: TUserData, }, - /// Response to a `GetValue`. - GetValueRes { + /// Response to a `GetRecord` request. + GetRecordRes { /// The value that might have been found in our storage. - result: Option, + record: Option, /// Nodes that are closer to the key we were searching for. closer_peers: Vec, /// Identifier of the request that was made by the remote. @@ -338,17 +339,14 @@ pub enum KademliaHandlerIn { }, /// Put a value into the dht records. - PutValue { - /// The key of the record. - key: Multihash, - /// The value of the record. - value: Vec, + PutRecord { + record: Record, /// Custom data. Passed back in the out event when the results arrive. user_data: TUserData, }, - /// Response to a `PutValue`. - PutValueRes { + /// Response to a `PutRecord`. + PutRecordRes { /// Key of the value that was put. key: Multihash, /// Value that was put. @@ -469,9 +467,18 @@ where .push(SubstreamState::InWaitingMessage(connec_unique_id, protocol)); } - #[inline] fn inject_event(&mut self, message: KademliaHandlerIn) { match message { + KademliaHandlerIn::Reset(request_id) => { + let pos = self.substreams.iter().position(|state| match state { + SubstreamState::InWaitingUser(conn_id, _) => + conn_id == &request_id.connec_unique_id, + _ => false, + }); + if let Some(pos) = pos { + let _ = self.substreams.remove(pos).try_close(); + } + } KademliaHandlerIn::FindNodeReq { key, user_data } => { let msg = KadRequestMsg::FindNode { key }; self.substreams.push(SubstreamState::OutPendingOpen(msg, Some(user_data.clone()))); @@ -532,31 +539,27 @@ where .push(SubstreamState::InPendingSend(conn_id, substream, msg)); } } - KademliaHandlerIn::AddProvider { key, provider_peer } => { + KademliaHandlerIn::AddProvider { key, provider } => { let msg = KadRequestMsg::AddProvider { key: key.clone(), - provider_peer: provider_peer.clone(), + provider: provider.clone(), }; self.substreams .push(SubstreamState::OutPendingOpen(msg, None)); } - KademliaHandlerIn::GetValue { key, user_data } => { + KademliaHandlerIn::GetRecord { key, user_data } => { let msg = KadRequestMsg::GetValue { key }; self.substreams .push(SubstreamState::OutPendingOpen(msg, Some(user_data))); } - KademliaHandlerIn::PutValue { key, value, user_data } => { - let msg = KadRequestMsg::PutValue { - key, - value, - }; - + KademliaHandlerIn::PutRecord { record, user_data } => { + let msg = KadRequestMsg::PutValue { record }; self.substreams .push(SubstreamState::OutPendingOpen(msg, Some(user_data))); } - KademliaHandlerIn::GetValueRes { - result, + KademliaHandlerIn::GetRecordRes { + record, closer_peers, request_id, } => { @@ -573,14 +576,14 @@ where }; let msg = KadResponseMsg::GetValue { - result, + record, closer_peers: closer_peers.clone(), }; self.substreams .push(SubstreamState::InPendingSend(conn_id, substream, msg)); } } - KademliaHandlerIn::PutValueRes { + KademliaHandlerIn::PutRecordRes { key, request_id, value, @@ -880,16 +883,15 @@ fn process_kad_request( key, request_id: KademliaRequestId { connec_unique_id }, }), - KadRequestMsg::AddProvider { key, provider_peer } => { - Ok(KademliaHandlerEvent::AddProvider { key, provider_peer }) + KadRequestMsg::AddProvider { key, provider } => { + Ok(KademliaHandlerEvent::AddProvider { key, provider }) } - KadRequestMsg::GetValue { key } => Ok(KademliaHandlerEvent::GetValue { + KadRequestMsg::GetValue { key } => Ok(KademliaHandlerEvent::GetRecord { key, request_id: KademliaRequestId { connec_unique_id }, }), - KadRequestMsg::PutValue { key, value } => Ok(KademliaHandlerEvent::PutValue { - key, - value, + KadRequestMsg::PutValue { record } => Ok(KademliaHandlerEvent::PutRecord { + record, request_id: KademliaRequestId { connec_unique_id }, }) } @@ -924,16 +926,17 @@ fn process_kad_response( user_data, }, KadResponseMsg::GetValue { - result, + record, closer_peers, - } => KademliaHandlerEvent::GetValueRes { - result, + } => KademliaHandlerEvent::GetRecordRes { + record, closer_peers, user_data, }, - KadResponseMsg::PutValue { key, .. } => { - KademliaHandlerEvent::PutValueRes { + KadResponseMsg::PutValue { key, value, .. } => { + KademliaHandlerEvent::PutRecordRes { key, + value, user_data, } } diff --git a/protocols/kad/src/jobs.rs b/protocols/kad/src/jobs.rs new file mode 100644 index 000000000000..ac24f3309c5f --- /dev/null +++ b/protocols/kad/src/jobs.rs @@ -0,0 +1,359 @@ +// Copyright 2019 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 crate::record::{Record, ProviderRecord, store::RecordStore}; + +use libp2p_core::PeerId; +use futures::prelude::*; +use multihash::Multihash; +use std::collections::HashSet; +use std::time::Duration; +use std::vec; +use wasm_timer::{Instant, Delay}; + +/// The maximum number of queries towards which background jobs +/// are allowed to start new queries on an invocation of +/// `Kademlia::poll`. +pub const JOBS_MAX_QUERIES: usize = 100; + +/// The maximum number of new queries started by a background job +/// per invocation of `Kademlia::poll`. +pub const JOBS_MAX_NEW_QUERIES: usize = 10; + +/// A background job run periodically. +#[derive(Debug)] +struct PeriodicJob { + interval: Duration, + state: PeriodicJobState, +} + +impl PeriodicJob { + fn is_running(&self) -> bool { + match self.state { + PeriodicJobState::Running(..) => true, + PeriodicJobState::Waiting(..) => false, + } + } + + /// Cuts short the remaining delay, if the job is currently waiting + /// for the delay to expire. + fn asap(&mut self) { + if let PeriodicJobState::Waiting(delay) = &mut self.state { + delay.reset(Instant::now() - Duration::from_secs(1)) + } + } + + /// Returns `true` if the job is currently not running but ready + /// to be run, `false` otherwise. + fn is_ready(&mut self, now: Instant) -> bool { + if let PeriodicJobState::Waiting(delay) = &mut self.state { + if now >= delay.deadline() || delay.poll().map(|a| a.is_ready()).unwrap_or(false) { + return true + } + } + false + } +} + +/// The state of a background job run periodically. +#[derive(Debug)] +enum PeriodicJobState { + Running(T), + Waiting(Delay) +} + +////////////////////////////////////////////////////////////////////////////// +// PutRecordJob + +/// Periodic job for replicating / publishing records. +pub struct PutRecordJob { + local_id: PeerId, + next_publish: Option, + publish_interval: Option, + record_ttl: Option, + skipped: HashSet, + inner: PeriodicJob>, +} + +impl PutRecordJob { + /// Creates a new periodic job for replicating and re-publishing + /// locally stored records. + pub fn new( + local_id: PeerId, + replicate_interval: Duration, + publish_interval: Option, + record_ttl: Option, + ) -> Self { + let now = Instant::now(); + let delay = Delay::new(now + replicate_interval); + let next_publish = publish_interval.map(|i| now + i); + Self { + local_id, + next_publish, + publish_interval, + record_ttl, + skipped: HashSet::new(), + inner: PeriodicJob { + interval: replicate_interval, + state: PeriodicJobState::Waiting(delay) + } + } + } + + /// Adds the key of a record that is ignored on the current or + /// next run of the job. + pub fn skip(&mut self, key: Multihash) { + self.skipped.insert(key); + } + + /// Checks whether the job is currently running. + pub fn is_running(&self) -> bool { + self.inner.is_running() + } + + /// Cuts short the remaining delay, if the job is currently waiting + /// for the delay to expire. + /// + /// The job is guaranteed to run on the next invocation of `poll`. + pub fn asap(&mut self, publish: bool) { + if publish { + self.next_publish = Some(Instant::now() - Duration::from_secs(1)) + } + self.inner.asap() + } + + /// Polls the job for records to replicate. + /// + /// Must be called in the context of a task. When `NotReady` is returned, + /// the current task is registered to be notified when the job is ready + /// to be run. + pub fn poll(&mut self, store: &mut T, now: Instant) -> Async + where + for<'a> T: RecordStore<'a> + { + if self.inner.is_ready(now) { + let publish = self.next_publish.map_or(false, |t_pub| now >= t_pub); + let records = store.records() + .filter_map(|r| { + let is_publisher = r.publisher.as_ref() == Some(&self.local_id); + if self.skipped.contains(&r.key) || (!publish && is_publisher) { + None + } else { + let mut record = r.into_owned(); + if publish && is_publisher { + record.expires = record.expires.or_else(|| + self.record_ttl.map(|ttl| now + ttl)); + } + Some(record) + } + }) + .collect::>() + .into_iter(); + + // Schedule the next publishing run. + if publish { + self.next_publish = self.publish_interval.map(|i| now + i); + } + + self.inner.state = PeriodicJobState::Running(records); + } + + if let PeriodicJobState::Running(records) = &mut self.inner.state { + loop { + if let Some(r) = records.next() { + if r.is_expired(now) { + store.remove(&r.key) + } else { + return Async::Ready(r) + } + } else { + break + } + } + + // Wait for the next run. + self.skipped.clear(); + let delay = Delay::new(now + self.inner.interval); + self.inner.state = PeriodicJobState::Waiting(delay); + } + + Async::NotReady + } +} + +////////////////////////////////////////////////////////////////////////////// +// AddProviderJob + +/// Periodic job for replicating provider records. +pub struct AddProviderJob { + inner: PeriodicJob> +} + +impl AddProviderJob { + /// Creates a new periodic job for provider announcements. + pub fn new(interval: Duration) -> Self { + let now = Instant::now(); + Self { + inner: PeriodicJob { + interval, + state: PeriodicJobState::Waiting(Delay::new(now + interval)) + } + } + } + + /// Checks whether the job is currently running. + pub fn is_running(&self) -> bool { + self.inner.is_running() + } + + /// Cuts short the remaining delay, if the job is currently waiting + /// for the delay to expire. + /// + /// The job is guaranteed to run on the next invocation of `poll`. + pub fn asap(&mut self) { + self.inner.asap() + } + + /// Polls the job for provider records to replicate. + /// + /// Must be called in the context of a task. When `NotReady` is returned, + /// the current task is registered to be notified when the job is ready + /// to be run. + pub fn poll(&mut self, store: &mut T, now: Instant) -> Async + where + for<'a> T: RecordStore<'a> + { + if self.inner.is_ready(now) { + let records = store.provided() + .map(|r| r.into_owned()) + .collect::>() + .into_iter(); + self.inner.state = PeriodicJobState::Running(records); + } + + if let PeriodicJobState::Running(keys) = &mut self.inner.state { + loop { + if let Some(r) = keys.next() { + if r.is_expired(now) { + store.remove_provider(&r.key, &r.provider) + } else { + return Async::Ready(r) + } + } else { + break + } + } + let delay = Delay::new(now + self.inner.interval); + self.inner.state = PeriodicJobState::Waiting(delay); + } + + Async::NotReady + } +} + +#[cfg(test)] +mod tests { + use crate::record::store::memory::MemoryStore; + use quickcheck::*; + use rand::Rng; + use super::*; + + fn rand_put_record_job() -> PutRecordJob { + let mut rng = rand::thread_rng(); + let id = PeerId::random(); + let replicate_interval = Duration::from_secs(rng.gen_range(1, 60)); + let publish_interval = Some(replicate_interval * rng.gen_range(1, 10)); + let record_ttl = Some(Duration::from_secs(rng.gen_range(1, 600))); + PutRecordJob::new(id.clone(), replicate_interval, publish_interval, record_ttl) + } + + fn rand_add_provider_job() -> AddProviderJob { + let mut rng = rand::thread_rng(); + let interval = Duration::from_secs(rng.gen_range(1, 60)); + AddProviderJob::new(interval) + } + + #[test] + fn new_job_not_running() { + let job = rand_put_record_job(); + assert!(!job.is_running()); + let job = rand_add_provider_job(); + assert!(!job.is_running()); + } + + #[test] + fn run_put_record_job() { + fn prop(records: Vec) { + let mut job = rand_put_record_job(); + // Fill a record store. + let mut store = MemoryStore::new(job.local_id.clone()); + for r in records { + let _ = store.put(r); + } + // Polling with an instant beyond the deadline for the next run + // is guaranteed to run the job, without the job needing to poll the `Delay` + // and thus without needing to run `poll` in the context of a task + // for testing purposes. + let now = Instant::now() + job.inner.interval; + // All (non-expired) records in the store must be yielded by the job. + for r in store.records().map(|r| r.into_owned()).collect::>() { + if !r.is_expired(now) { + assert_eq!(job.poll(&mut store, now), Async::Ready(r)); + assert!(job.is_running()); + } + } + assert_eq!(job.poll(&mut store, now), Async::NotReady); + assert!(!job.is_running()); + } + + quickcheck(prop as fn(_)) + } + + #[test] + fn run_add_provider_job() { + fn prop(records: Vec) { + let mut job = rand_add_provider_job(); + let id = PeerId::random(); + // Fill a record store. + let mut store = MemoryStore::new(id.clone()); + for mut r in records { + r.provider = id.clone(); + let _ = store.add_provider(r); + } + // Polling with an instant beyond the deadline for the next run + // is guaranteed to run the job, without the job needing to poll the `Delay` + // and thus without needing to run `poll` in the context of a task + // for testing purposes. + let now = Instant::now() + job.inner.interval; + // All (non-expired) records in the store must be yielded by the job. + for r in store.provided().map(|r| r.into_owned()).collect::>() { + if !r.is_expired(now) { + assert_eq!(job.poll(&mut store, now), Async::Ready(r)); + assert!(job.is_running()); + } + } + assert_eq!(job.poll(&mut store, now), Async::NotReady); + assert!(!job.is_running()); + } + + quickcheck(prop as fn(_)) + } +} + diff --git a/protocols/kad/src/kbucket.rs b/protocols/kad/src/kbucket.rs index b968af2ca76f..1fbe04296b20 100644 --- a/protocols/kad/src/kbucket.rs +++ b/protocols/kad/src/kbucket.rs @@ -83,19 +83,19 @@ const NUM_BUCKETS: usize = 256; /// A `KBucketsTable` represents a Kademlia routing table. #[derive(Debug, Clone)] -pub struct KBucketsTable { +pub struct KBucketsTable { /// The key identifying the local peer that owns the routing table. - local_key: Key, + local_key: TKey, /// The buckets comprising the routing table. - buckets: Vec>, + buckets: Vec>, /// The list of evicted entries that have been replaced with pending /// entries since the last call to [`KBucketsTable::take_applied_pending`]. - applied_pending: VecDeque> + applied_pending: VecDeque> } /// A (type-safe) index into a `KBucketsTable`, i.e. a non-negative integer in the /// interval `[0, NUM_BUCKETS)`. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] struct BucketIndex(usize); impl BucketIndex { @@ -124,17 +124,18 @@ impl BucketIndex { for i in 0 .. quot { bytes[31 - i] = rng.gen(); } - let rem = self.0 % 8; - let lower = usize::pow(2, rem as u32); - let upper = usize::pow(2, rem as u32 + 1); + let rem = (self.0 % 8) as u32; + let lower = usize::pow(2, rem); + let upper = usize::pow(2, rem + 1); bytes[31 - quot] = rng.gen_range(lower, upper) as u8; Distance(bigint::U256::from(bytes)) } } -impl KBucketsTable +impl KBucketsTable where - TPeerId: Clone, + TKey: Clone + AsRef, + TVal: Clone { /// Creates a new, empty Kademlia routing table with entries partitioned /// into buckets as per the Kademlia protocol. @@ -142,7 +143,7 @@ where /// The given `pending_timeout` specifies the duration after creation of /// a [`PendingEntry`] after which it becomes eligible for insertion into /// a full bucket, replacing the least-recently (dis)connected node. - pub fn new(local_key: Key, pending_timeout: Duration) -> Self { + pub fn new(local_key: TKey, pending_timeout: Duration) -> Self { KBucketsTable { local_key, buckets: (0 .. NUM_BUCKETS).map(|_| KBucket::new(pending_timeout)).collect(), @@ -151,14 +152,14 @@ where } /// Returns the local key. - pub fn local_key(&self) -> &Key { + pub fn local_key(&self) -> &TKey { &self.local_key } /// Returns an `Entry` for the given key, representing the state of the entry /// in the routing table. - pub fn entry<'a>(&'a mut self, key: &'a Key) -> Entry<'a, TPeerId, TVal> { - let index = BucketIndex::new(&self.local_key.distance(key)); + pub fn entry<'a>(&'a mut self, key: &'a TKey) -> Entry<'a, TKey, TVal> { + let index = BucketIndex::new(&self.local_key.as_ref().distance(key)); if let Some(i) = index { let bucket = &mut self.buckets[i.get()]; if let Some(applied) = bucket.apply_pending() { @@ -171,7 +172,7 @@ where } /// Returns an iterator over all the entries in the routing table. - pub fn iter<'a>(&'a mut self) -> impl Iterator> { + pub fn iter<'a>(&'a mut self) -> impl Iterator> { let applied_pending = &mut self.applied_pending; self.buckets.iter_mut().flat_map(move |table| { if let Some(applied) = table.apply_pending() { @@ -194,7 +195,7 @@ where /// /// The buckets are ordered by proximity to the `local_key`, i.e. the first /// bucket is the closest bucket (containing at most one key). - pub fn buckets<'a>(&'a mut self) -> impl Iterator> + 'a { + pub fn buckets<'a>(&'a mut self) -> impl Iterator> + 'a { let applied_pending = &mut self.applied_pending; self.buckets.iter_mut().enumerate().map(move |(i, b)| { if let Some(applied) = b.apply_pending() { @@ -219,24 +220,24 @@ where /// buckets are updated accordingly. The fact that a pending entry was applied is /// recorded in the `KBucketsTable` in the form of `AppliedPending` results, which must be /// consumed by calling this function. - pub fn take_applied_pending(&mut self) -> Option> { + pub fn take_applied_pending(&mut self) -> Option> { self.applied_pending.pop_front() } /// Returns an iterator over the keys closest to `target`, ordered by /// increasing distance. - pub fn closest_keys<'a, T>(&'a mut self, target: &'a Key) - -> impl Iterator> + 'a + pub fn closest_keys<'a, T>(&'a mut self, target: &'a T) + -> impl Iterator + 'a where - T: Clone + T: Clone + AsRef { - let distance = self.local_key.distance(target); + let distance = self.local_key.as_ref().distance(target); ClosestIter { target, iter: None, table: self, buckets_iter: ClosestBucketsIter::new(distance), - fmap: |b: &KBucket<_, _>| -> ArrayVec<_> { + fmap: |b: &KBucket| -> ArrayVec<_> { b.iter().map(|(n,_)| n.key.clone()).collect() } } @@ -244,13 +245,13 @@ where /// Returns an iterator over the nodes closest to the `target` key, ordered by /// increasing distance. - pub fn closest<'a, T>(&'a mut self, target: &'a Key) - -> impl Iterator> + 'a + pub fn closest<'a, T>(&'a mut self, target: &'a T) + -> impl Iterator> + 'a where - T: Clone, + T: Clone + AsRef, TVal: Clone { - let distance = self.local_key.distance(target); + let distance = self.local_key.as_ref().distance(target); ClosestIter { target, iter: None, @@ -264,18 +265,41 @@ where } } } + + /// Counts the number of nodes between the local node and the node + /// closest to `target`. + /// + /// The number of nodes between the local node and the target are + /// calculated by backtracking from the target towards the local key. + pub fn count_nodes_between(&mut self, target: &T) -> usize + where + T: AsRef + { + let local_key = self.local_key.clone(); + let distance = target.as_ref().distance(&local_key); + let mut iter = ClosestBucketsIter::new(distance).take_while(|i| i.get() != 0); + if let Some(i) = iter.next() { + let num_first = self.buckets[i.get()].iter() + .filter(|(n,_)| n.key.as_ref().distance(&local_key) <= distance) + .count(); + let num_rest: usize = iter.map(|i| self.buckets[i.get()].num_entries()).sum(); + num_first + num_rest + } else { + 0 + } + } } /// An iterator over (some projection of) the closest entries in a /// `KBucketsTable` w.r.t. some target `Key`. -struct ClosestIter<'a, TTarget, TPeerId, TVal, TMap, TOut> { +struct ClosestIter<'a, TTarget, TKey, TVal, TMap, TOut> { /// A reference to the target key whose distance to the local key determines /// the order in which the buckets are traversed. The resulting /// array from projecting the entries of each bucket using `fmap` is /// sorted according to the distance to the target. - target: &'a Key, + target: &'a TTarget, /// A reference to all buckets of the `KBucketsTable`. - table: &'a mut KBucketsTable, + table: &'a mut KBucketsTable, /// The iterator over the bucket indices in the order determined by the /// distance of the local key to the target. buckets_iter: ClosestBucketsIter, @@ -376,12 +400,14 @@ impl Iterator for ClosestBucketsIter { } } -impl Iterator -for ClosestIter<'_, TTarget, TPeerId, TVal, TMap, TOut> +impl Iterator +for ClosestIter<'_, TTarget, TKey, TVal, TMap, TOut> where - TPeerId: Clone, - TMap: Fn(&KBucket) -> ArrayVec<[TOut; K_VALUE]>, - TOut: AsRef> + TTarget: AsRef, + TKey: Clone + AsRef, + TVal: Clone, + TMap: Fn(&KBucket) -> ArrayVec<[TOut; K_VALUE]>, + TOut: AsRef { type Item = TOut; @@ -400,8 +426,8 @@ where } let mut v = (self.fmap)(bucket); v.sort_by(|a, b| - self.target.distance(a.as_ref()) - .cmp(&self.target.distance(b.as_ref()))); + self.target.as_ref().distance(a.as_ref()) + .cmp(&self.target.as_ref().distance(b.as_ref()))); self.iter = Some(v.into_iter()); } else { return None @@ -418,9 +444,10 @@ pub struct KBucketRef<'a, TPeerId, TVal> { bucket: &'a mut KBucket } -impl KBucketRef<'_, TPeerId, TVal> +impl KBucketRef<'_, TKey, TVal> where - TPeerId: Clone + TKey: Clone + AsRef, + TVal: Clone { /// Returns the number of entries in the bucket. pub fn num_entries(&self) -> usize { @@ -432,6 +459,7 @@ where self.bucket.pending().map_or(false, |n| !n.is_ready()) } + /// Tests whether the given distance falls into this bucket. pub fn contains(&self, d: &Distance) -> bool { BucketIndex::new(d).map_or(false, |i| i == self.index) } @@ -453,6 +481,34 @@ mod tests { use super::*; use libp2p_core::PeerId; use quickcheck::*; + use rand::Rng; + + type TestTable = KBucketsTable; + + impl Arbitrary for TestTable { + fn arbitrary(g: &mut G) -> TestTable { + let local_key = Key::from(PeerId::random()); + let timeout = Duration::from_secs(g.gen_range(1, 360)); + let mut table = TestTable::new(local_key.clone().into(), timeout); + let mut num_total = g.gen_range(0, 100); + for (i, b) in &mut table.buckets.iter_mut().enumerate().rev() { + let ix = BucketIndex(i); + let num = g.gen_range(0, usize::min(K_VALUE, num_total) + 1); + num_total -= num; + for _ in 0 .. num { + let distance = ix.rand_distance(g); + let key = local_key.for_distance(distance); + let node = Node { key: key.clone(), value: () }; + let status = NodeStatus::arbitrary(g); + match b.insert(node, status) { + InsertResult::Inserted => {} + _ => panic!() + } + } + } + table + } + } #[test] fn rand_distance() { @@ -469,7 +525,7 @@ mod tests { } #[test] - fn basic_closest() { + fn entry_inserted() { let local_key = Key::from(PeerId::random()); let other_id = Key::from(PeerId::random()); @@ -489,7 +545,7 @@ mod tests { } #[test] - fn update_local_id_fails() { + fn entry_self() { let local_key = Key::from(PeerId::random()); let mut table = KBucketsTable::<_, ()>::new(local_key.clone(), Duration::from_secs(5)); match table.entry(&local_key) { @@ -545,7 +601,7 @@ mod tests { match e.insert((), NodeStatus::Connected) { InsertResult::Pending { disconnected } => { expected_applied = AppliedPending { - inserted: key.clone(), + inserted: Node { key: key.clone(), value: () }, evicted: Some(Node { key: disconnected, value: () }) }; full_bucket_index = BucketIndex::new(&key.distance(&local_key)); @@ -569,7 +625,7 @@ mod tests { let elapsed = Instant::now() - Duration::from_secs(1); full_bucket.pending_mut().unwrap().set_ready_at(elapsed); - match table.entry(&expected_applied.inserted) { + match table.entry(&expected_applied.inserted.key) { Entry::Present(_, NodeStatus::Connected) => {} x => panic!("Unexpected entry: {:?}", x) } @@ -582,4 +638,28 @@ mod tests { assert_eq!(Some(expected_applied), table.take_applied_pending()); assert_eq!(None, table.take_applied_pending()); } + + #[test] + fn count_nodes_between() { + fn prop(mut table: TestTable, target: Key) -> bool { + let num_to_target = table.count_nodes_between(&target); + let distance = table.local_key.distance(&target); + let base2 = U256::from(2); + let mut iter = ClosestBucketsIter::new(distance); + iter.all(|i| { + // Flip the distance bit related to the bucket. + let d = Distance(distance.0 ^ (base2.pow(U256::from(i.get())))); + let k = table.local_key.for_distance(d); + if distance.0.bit(i.get()) { + // Bit flip `1` -> `0`, the key must be closer than `target`. + d < distance && table.count_nodes_between(&k) <= num_to_target + } else { + // Bit flip `0` -> `1`, the key must be farther than `target`. + d > distance && table.count_nodes_between(&k) >= num_to_target + } + }) + } + + QuickCheck::new().tests(10).quickcheck(prop as fn(_,_) -> _) + } } diff --git a/protocols/kad/src/kbucket/bucket.rs b/protocols/kad/src/kbucket/bucket.rs index 66ea14075e74..b8fca6ae347a 100644 --- a/protocols/kad/src/kbucket/bucket.rs +++ b/protocols/kad/src/kbucket/bucket.rs @@ -30,9 +30,9 @@ use super::*; /// A `PendingNode` is a `Node` that is pending insertion into a `KBucket`. #[derive(Debug, Clone)] -pub struct PendingNode { +pub struct PendingNode { /// The pending node to insert. - node: Node, + node: Node, /// The status of the pending node. status: NodeStatus, @@ -54,8 +54,8 @@ pub enum NodeStatus { Disconnected } -impl PendingNode { - pub fn key(&self) -> &Key { +impl PendingNode { + pub fn key(&self) -> &TKey { &self.node.key } @@ -80,9 +80,9 @@ impl PendingNode { /// in the Kademlia DHT together with an associated value (e.g. contact /// information). #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Node { +pub struct Node { /// The key of the node, identifying the peer. - pub key: Key, + pub key: TKey, /// The associated value. pub value: TVal, } @@ -92,12 +92,12 @@ pub struct Node { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Position(usize); -/// A `KBucket` is a list of up to `K_VALUE` `Key`s and associated values, +/// A `KBucket` is a list of up to `K_VALUE` keys and associated values, /// ordered from least-recently connected to most-recently connected. #[derive(Debug, Clone)] -pub struct KBucket { +pub struct KBucket { /// The nodes contained in the bucket. - nodes: ArrayVec<[Node; K_VALUE]>, + nodes: ArrayVec<[Node; K_VALUE]>, /// The position (index) in `nodes` that marks the first connected node. /// @@ -116,7 +116,7 @@ pub struct KBucket { /// A node that is pending to be inserted into a full bucket, should the /// least-recently connected (and currently disconnected) node not be /// marked as connected within `unresponsive_timeout`. - pending: Option>, + pending: Option>, /// The timeout window before a new pending node is eligible for insertion, /// if the least-recently connected node is not updated as being connected @@ -127,7 +127,7 @@ pub struct KBucket { /// The result of inserting an entry into a bucket. #[must_use] #[derive(Debug, Clone, PartialEq, Eq)] -pub enum InsertResult { +pub enum InsertResult { /// The entry has been successfully inserted. Inserted, /// The entry is pending insertion because the relevant bucket is currently full. @@ -140,7 +140,7 @@ pub enum InsertResult { /// in order to prevent it from being evicted. If connectivity to the peer is /// re-established, the corresponding entry should be updated with /// [`NodeStatus::Connected`]. - disconnected: Key + disconnected: TKey }, /// The entry was not inserted because the relevant bucket is full. Full @@ -149,17 +149,18 @@ pub enum InsertResult { /// The result of applying a pending node to a bucket, possibly /// replacing an existing node. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct AppliedPending { +pub struct AppliedPending { /// The key of the inserted pending node. - pub inserted: Key, + pub inserted: Node, /// The node that has been evicted from the bucket to make room for the /// pending node, if any. - pub evicted: Option> + pub evicted: Option> } -impl KBucket +impl KBucket where - TPeerId: Clone + TKey: Clone + AsRef, + TVal: Clone { /// Creates a new `KBucket` with the given timeout for pending entries. pub fn new(pending_timeout: Duration) -> Self { @@ -172,28 +173,28 @@ where } /// Returns a reference to the pending node of the bucket, if there is any. - pub fn pending(&self) -> Option<&PendingNode> { + pub fn pending(&self) -> Option<&PendingNode> { self.pending.as_ref() } /// Returns a mutable reference to the pending node of the bucket, if there is any. - pub fn pending_mut(&mut self) -> Option<&mut PendingNode> { + pub fn pending_mut(&mut self) -> Option<&mut PendingNode> { self.pending.as_mut() } /// Returns a reference to the pending node of the bucket, if there is any /// with a matching key. - pub fn as_pending(&self, key: &Key) -> Option<&PendingNode> { - self.pending().filter(|p| &p.node.key == key) + pub fn as_pending(&self, key: &TKey) -> Option<&PendingNode> { + self.pending().filter(|p| p.node.key.as_ref() == key.as_ref()) } /// Returns a reference to a node in the bucket. - pub fn get(&self, key: &Key) -> Option<&Node> { + pub fn get(&self, key: &TKey) -> Option<&Node> { self.position(key).map(|p| &self.nodes[p.0]) } /// Returns an iterator over the nodes in the bucket, together with their status. - pub fn iter(&self) -> impl Iterator, NodeStatus)> { + pub fn iter(&self) -> impl Iterator, NodeStatus)> { self.nodes.iter().enumerate().map(move |(p, n)| (n, self.status(Position(p)))) } @@ -203,7 +204,7 @@ where /// If a pending node has been inserted, its key is returned together with /// the node that was replaced. `None` indicates that the nodes in the /// bucket remained unchanged. - pub fn apply_pending(&mut self) -> Option> { + pub fn apply_pending(&mut self) -> Option> { if let Some(pending) = self.pending.take() { if pending.replace <= Instant::now() { if self.nodes.is_full() { @@ -212,7 +213,7 @@ where return None } // The pending node will be inserted. - let inserted = pending.node.key.clone(); + let inserted = pending.node.clone(); // A connected pending node goes at the end of the list for // the connected peers, removing the least-recently connected. if pending.status == NodeStatus::Connected { @@ -241,7 +242,7 @@ where } } else { // There is room in the bucket, so just insert the pending node. - let inserted = pending.node.key.clone(); + let inserted = pending.node.clone(); match self.insert(pending.node, pending.status) { InsertResult::Inserted => return Some(AppliedPending { inserted, evicted: None }), @@ -265,7 +266,7 @@ where /// Updates the status of the node referred to by the given key, if it is /// in the bucket. - pub fn update(&mut self, key: &Key, status: NodeStatus) { + pub fn update(&mut self, key: &TKey, status: NodeStatus) { // Remove the node from its current position and then reinsert it // with the desired status, which puts it at the end of either the // prefix list of disconnected nodes or the suffix list of connected @@ -318,7 +319,7 @@ where /// i.e. as the most-recently disconnected node. If there are no connected nodes, /// the new node is added as the last element of the bucket. /// - pub fn insert(&mut self, node: Node, status: NodeStatus) -> InsertResult { + pub fn insert(&mut self, node: Node, status: NodeStatus) -> InsertResult { match status { NodeStatus::Connected => { if self.nodes.is_full() { @@ -385,16 +386,16 @@ where } /// Gets the position of an node in the bucket. - pub fn position(&self, key: &Key) -> Option { - self.nodes.iter().position(|p| &p.key == key).map(Position) + pub fn position(&self, key: &TKey) -> Option { + self.nodes.iter().position(|p| p.key.as_ref() == key.as_ref()).map(Position) } /// Gets a mutable reference to the node identified by the given key. /// /// Returns `None` if the given key does not refer to an node in the /// bucket. - pub fn get_mut(&mut self, key: &Key) -> Option<&mut Node> { - self.nodes.iter_mut().find(move |p| &p.key == key) + pub fn get_mut(&mut self, key: &TKey) -> Option<&mut Node> { + self.nodes.iter_mut().find(move |p| p.key.as_ref() == key.as_ref()) } } @@ -406,10 +407,10 @@ mod tests { use super::*; use quickcheck::*; - impl Arbitrary for KBucket { - fn arbitrary(g: &mut G) -> KBucket { + impl Arbitrary for KBucket, ()> { + fn arbitrary(g: &mut G) -> KBucket, ()> { let timeout = Duration::from_secs(g.gen_range(1, g.size() as u64)); - let mut bucket = KBucket::::new(timeout); + let mut bucket = KBucket::, ()>::new(timeout); let num_nodes = g.gen_range(1, K_VALUE + 1); for _ in 0 .. num_nodes { let key = Key::new(PeerId::random()); @@ -441,7 +442,7 @@ mod tests { } // Fill a bucket with random nodes with the given status. - fn fill_bucket(bucket: &mut KBucket, status: NodeStatus) { + fn fill_bucket(bucket: &mut KBucket, ()>, status: NodeStatus) { let num_entries_start = bucket.num_entries(); for i in 0 .. K_VALUE - num_entries_start { let key = Key::new(PeerId::random()); @@ -454,7 +455,7 @@ mod tests { #[test] fn ordering() { fn prop(status: Vec) -> bool { - let mut bucket = KBucket::::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); // The expected lists of connected and disconnected nodes. let mut connected = VecDeque::new(); @@ -503,7 +504,7 @@ mod tests { #[test] fn full_bucket() { - let mut bucket = KBucket::::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); // Fill the bucket with disconnected nodes. fill_bucket(&mut bucket, NodeStatus::Disconnected); @@ -545,7 +546,7 @@ mod tests { pending.set_ready_at(Instant::now() - Duration::from_secs(1)); let result = bucket.apply_pending(); assert_eq!(result, Some(AppliedPending { - inserted: key.clone(), + inserted: node.clone(), evicted: Some(first_disconnected) })); assert_eq!(Some((&node, NodeStatus::Connected)), bucket.iter().last()); @@ -567,7 +568,7 @@ mod tests { #[test] fn full_bucket_discard_pending() { - let mut bucket = KBucket::::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); fill_bucket(&mut bucket, NodeStatus::Disconnected); let (first, _) = bucket.iter().next().unwrap(); let first_disconnected = first.clone(); @@ -599,7 +600,7 @@ mod tests { #[test] fn bucket_update() { - fn prop(mut bucket: KBucket, pos: Position, status: NodeStatus) -> bool { + fn prop(mut bucket: KBucket, ()>, pos: Position, status: NodeStatus) -> bool { let num_nodes = bucket.num_entries(); // Capture position and key of the random node to update. diff --git a/protocols/kad/src/kbucket/entry.rs b/protocols/kad/src/kbucket/entry.rs index 3943c15bdbce..4bb986718a03 100644 --- a/protocols/kad/src/kbucket/entry.rs +++ b/protocols/kad/src/kbucket/entry.rs @@ -35,15 +35,15 @@ pub struct EntryRefView<'a, TPeerId, TVal> { } /// An immutable by-reference view of a `Node`. -pub struct NodeRefView<'a, TPeerId, TVal> { - pub key: &'a Key, +pub struct NodeRefView<'a, TKey, TVal> { + pub key: &'a TKey, pub value: &'a TVal } -impl EntryRefView<'_, TPeerId, TVal> { - pub fn to_owned(&self) -> EntryView +impl EntryRefView<'_, TKey, TVal> { + pub fn to_owned(&self) -> EntryView where - TPeerId: Clone, + TKey: Clone, TVal: Clone { EntryView { @@ -59,16 +59,16 @@ impl EntryRefView<'_, TPeerId, TVal> { /// A cloned, immutable view of an entry that is either present in a bucket /// or pending insertion. #[derive(Clone, Debug)] -pub struct EntryView { +pub struct EntryView { /// The node represented by the entry. - pub node: Node, + pub node: Node, /// The status of the node. pub status: NodeStatus } -impl AsRef> for EntryView { - fn as_ref(&self) -> &Key { - &self.node.key +impl, TVal> AsRef for EntryView { + fn as_ref(&self) -> &KeyBytes { + self.node.key.as_ref() } } @@ -88,17 +88,18 @@ pub enum Entry<'a, TPeerId, TVal> { /// The internal representation of the different states of an `Entry`, /// referencing the associated key and bucket. #[derive(Debug)] -struct EntryRef<'a, TPeerId, TVal> { - bucket: &'a mut KBucket, - key: &'a Key, +struct EntryRef<'a, TKey, TVal> { + bucket: &'a mut KBucket, + key: &'a TKey, } -impl<'a, TPeerId, TVal> Entry<'a, TPeerId, TVal> +impl<'a, TKey, TVal> Entry<'a, TKey, TVal> where - TPeerId: Clone, + TKey: Clone + AsRef, + TVal: Clone { /// Creates a new `Entry` for a `Key`, encapsulating access to a bucket. - pub(super) fn new(bucket: &'a mut KBucket, key: &'a Key) -> Self { + pub(super) fn new(bucket: &'a mut KBucket, key: &'a TKey) -> Self { if let Some(pos) = bucket.position(key) { let status = bucket.status(pos); Entry::Present(PresentEntry::new(bucket, key), status) @@ -114,7 +115,7 @@ where /// /// Returns `None` if the entry is neither present in a bucket nor /// pending insertion into a bucket. - pub fn view(&'a mut self) -> Option> { + pub fn view(&'a mut self) -> Option> { match self { Entry::Present(entry, status) => Some(EntryRefView { node: NodeRefView { @@ -139,7 +140,7 @@ where /// Returns `None` if the `Key` used to construct this `Entry` is not a valid /// key for an entry in a bucket, which is the case for the `local_key` of /// the `KBucketsTable` referring to the local node. - pub fn key(&self) -> Option<&Key> { + pub fn key(&self) -> Option<&TKey> { match self { Entry::Present(entry, _) => Some(entry.key()), Entry::Pending(entry, _) => Some(entry.key()), @@ -150,7 +151,7 @@ where /// Returns the value associated with the entry. /// - /// Returns `None` if the entry absent from any bucket or refers to the + /// Returns `None` if the entry is absent from any bucket or refers to the /// local node. pub fn value(&mut self) -> Option<&mut TVal> { match self { @@ -164,18 +165,19 @@ where /// An entry present in a bucket. #[derive(Debug)] -pub struct PresentEntry<'a, TPeerId, TVal>(EntryRef<'a, TPeerId, TVal>); +pub struct PresentEntry<'a, TKey, TVal>(EntryRef<'a, TKey, TVal>); -impl<'a, TPeerId, TVal> PresentEntry<'a, TPeerId, TVal> +impl<'a, TKey, TVal> PresentEntry<'a, TKey, TVal> where - TPeerId: Clone, + TKey: Clone + AsRef, + TVal: Clone { - fn new(bucket: &'a mut KBucket, key: &'a Key) -> Self { + fn new(bucket: &'a mut KBucket, key: &'a TKey) -> Self { PresentEntry(EntryRef { bucket, key }) } /// Returns the key of the entry. - pub fn key(&self) -> &Key { + pub fn key(&self) -> &TKey { self.0.key } @@ -196,18 +198,19 @@ where /// An entry waiting for a slot to be available in a bucket. #[derive(Debug)] -pub struct PendingEntry<'a, TPeerId, TVal>(EntryRef<'a, TPeerId, TVal>); +pub struct PendingEntry<'a, TKey, TVal>(EntryRef<'a, TKey, TVal>); -impl<'a, TPeerId, TVal> PendingEntry<'a, TPeerId, TVal> +impl<'a, TKey, TVal> PendingEntry<'a, TKey, TVal> where - TPeerId: Clone, + TKey: Clone + AsRef, + TVal: Clone { - fn new(bucket: &'a mut KBucket, key: &'a Key) -> Self { + fn new(bucket: &'a mut KBucket, key: &'a TKey) -> Self { PendingEntry(EntryRef { bucket, key }) } /// Returns the key of the entry. - pub fn key(&self) -> &Key { + pub fn key(&self) -> &TKey { self.0.key } @@ -220,7 +223,7 @@ where } /// Updates the status of the pending entry. - pub fn update(self, status: NodeStatus) -> PendingEntry<'a, TPeerId, TVal> { + pub fn update(self, status: NodeStatus) -> PendingEntry<'a, TKey, TVal> { self.0.bucket.update_pending(status); PendingEntry::new(self.0.bucket, self.0.key) } @@ -228,23 +231,24 @@ where /// An entry that is not present in any bucket. #[derive(Debug)] -pub struct AbsentEntry<'a, TPeerId, TVal>(EntryRef<'a, TPeerId, TVal>); +pub struct AbsentEntry<'a, TKey, TVal>(EntryRef<'a, TKey, TVal>); -impl<'a, TPeerId, TVal> AbsentEntry<'a, TPeerId, TVal> +impl<'a, TKey, TVal> AbsentEntry<'a, TKey, TVal> where - TPeerId: Clone, + TKey: Clone + AsRef, + TVal: Clone { - fn new(bucket: &'a mut KBucket, key: &'a Key) -> Self { + fn new(bucket: &'a mut KBucket, key: &'a TKey) -> Self { AbsentEntry(EntryRef { bucket, key }) } /// Returns the key of the entry. - pub fn key(&self) -> &Key { + pub fn key(&self) -> &TKey { self.0.key } /// Attempts to insert the entry into a bucket. - pub fn insert(self, value: TVal, status: NodeStatus) -> InsertResult { + pub fn insert(self, value: TVal, status: NodeStatus) -> InsertResult { self.0.bucket.insert(Node { key: self.0.key.clone(), value diff --git a/protocols/kad/src/kbucket/key.rs b/protocols/kad/src/kbucket/key.rs index 0e4691c1245e..49f45489abaa 100644 --- a/protocols/kad/src/kbucket/key.rs +++ b/protocols/kad/src/kbucket/key.rs @@ -21,67 +21,26 @@ use bigint::U256; use libp2p_core::PeerId; use multihash::Multihash; -use sha2::{Digest, Sha256, digest::generic_array::{GenericArray, typenum::U32}}; +use sha2::{Digest, Sha256}; +use sha2::digest::generic_array::{GenericArray, typenum::U32}; +use std::hash::{Hash, Hasher}; -/// A `Key` identifies both the nodes participating in the Kademlia DHT, as well as -/// records stored in the DHT. +/// A `Key` in the DHT keyspace with preserved preimage. /// -/// The set of all `Key`s defines the Kademlia keyspace. +/// Keys in the DHT keyspace identify both the participating nodes, as well as +/// the records stored in the DHT. /// /// `Key`s have an XOR metric as defined in the Kademlia paper, i.e. the bitwise XOR of /// the hash digests, interpreted as an integer. See [`Key::distance`]. -/// -/// A `Key` preserves the preimage of type `T` of the hash function. See [`Key::preimage`]. #[derive(Clone, Debug)] pub struct Key { preimage: T, bytes: KeyBytes, } -/// The raw bytes of a key in the DHT keyspace. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct KeyBytes(GenericArray); - -impl KeyBytes { - /// Computes the distance of the keys according to the XOR metric. - pub fn distance(&self, other: &U) -> Distance - where - U: AsRef - { - let a = U256::from(self.0.as_ref()); - let b = U256::from(other.as_ref().0.as_ref()); - Distance(a ^ b) - } -} - -impl AsRef for KeyBytes { - fn as_ref(&self) -> &KeyBytes { - self - } -} - -impl AsRef for Key { - fn as_ref(&self) -> &KeyBytes { - &self.bytes - } -} - -impl PartialEq> for Key { - fn eq(&self, other: &Key) -> bool { - self.bytes == other.bytes - } -} - -impl Eq for Key {} - -impl AsRef> for Key { - fn as_ref(&self) -> &Key { - self - } -} - impl Key { - /// Construct a new `Key` by hashing the bytes of the given `preimage`. + /// Constructs a new `Key` by running the given value through a random + /// oracle. /// /// The preimage of type `T` is preserved. See [`Key::preimage`] and /// [`Key::into_preimage`]. @@ -89,20 +48,10 @@ impl Key { where T: AsRef<[u8]> { - let bytes = KeyBytes(Sha256::digest(preimage.as_ref())); + let bytes = KeyBytes::new(&preimage); Key { preimage, bytes } } - /// Returns the uniquely determined key with the given distance to `self`. - /// - /// This implements the following equivalence: - /// - /// `self xor other = distance <==> other = self xor distance` - pub fn for_distance(&self, d: Distance) -> KeyBytes { - let key_int = U256::from(self.bytes.0.as_ref()) ^ d.0; - KeyBytes(GenericArray::from(<[u8; 32]>::from(key_int))) - } - /// Borrows the preimage of the key. pub fn preimage(&self) -> &T { &self.preimage @@ -120,6 +69,15 @@ impl Key { { self.bytes.distance(other) } + + /// Returns the uniquely determined key with the given distance to `self`. + /// + /// This implements the following equivalence: + /// + /// `self xor other = distance <==> other = self xor distance` + pub fn for_distance(&self, d: Distance) -> KeyBytes { + self.bytes.for_distance(d) + } } impl Into for Key { @@ -140,7 +98,68 @@ impl From for Key { } } -/// A distance between two `Key`s. +impl AsRef for Key { + fn as_ref(&self) -> &KeyBytes { + &self.bytes + } +} + +impl PartialEq> for Key { + fn eq(&self, other: &Key) -> bool { + self.bytes == other.bytes + } +} + +impl Eq for Key {} + +impl Hash for Key { + fn hash(&self, state: &mut H) { + self.bytes.0.hash(state); + } +} + +/// The raw bytes of a key in the DHT keyspace. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct KeyBytes(GenericArray); + +impl KeyBytes { + /// Creates a new key in the DHT keyspace by running the given + /// value through a random oracle. + pub fn new(value: T) -> Self + where + T: AsRef<[u8]> + { + KeyBytes(Sha256::digest(value.as_ref())) + } + + /// Computes the distance of the keys according to the XOR metric. + pub fn distance(&self, other: &U) -> Distance + where + U: AsRef + { + let a = U256::from(self.0.as_ref()); + let b = U256::from(other.as_ref().0.as_ref()); + Distance(a ^ b) + } + + /// Returns the uniquely determined key with the given distance to `self`. + /// + /// This implements the following equivalence: + /// + /// `self xor other = distance <==> other = self xor distance` + pub fn for_distance(&self, d: Distance) -> KeyBytes { + let key_int = U256::from(self.0.as_ref()) ^ d.0; + KeyBytes(GenericArray::from(<[u8; 32]>::from(key_int))) + } +} + +impl AsRef for KeyBytes { + fn as_ref(&self) -> &KeyBytes { + self + } +} + +/// A distance between two keys in the DHT keyspace. #[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Debug)] pub struct Distance(pub(super) bigint::U256); @@ -148,6 +167,7 @@ pub struct Distance(pub(super) bigint::U256); mod tests { use super::*; use quickcheck::*; + use multihash::Hash::SHA2256; impl Arbitrary for Key { fn arbitrary(_: &mut G) -> Key { @@ -155,6 +175,12 @@ mod tests { } } + impl Arbitrary for Key { + fn arbitrary(_: &mut G) -> Key { + Key::from(Multihash::random(SHA2256)) + } + } + #[test] fn identity() { fn prop(a: Key) -> bool { diff --git a/protocols/kad/src/lib.rs b/protocols/kad/src/lib.rs index 431175f09a9f..6ff92a5b14be 100644 --- a/protocols/kad/src/lib.rs +++ b/protocols/kad/src/lib.rs @@ -31,14 +31,14 @@ pub mod record; mod addresses; mod behaviour; +mod jobs; mod protobuf_structs; mod query; +pub use addresses::Addresses; pub use behaviour::*; pub use protocol::KadConnectionType; -pub use record::{RecordStore, RecordStorageError, MemoryRecordStorage}; - -use std::time::Duration; +pub use record::*; /// The `k` parameter of the Kademlia specification. /// @@ -66,6 +66,3 @@ pub const K_VALUE: usize = 20; /// The current value is `3`. pub const ALPHA_VALUE: usize = 3; -const KBUCKET_PENDING_TIMEOUT: Duration = Duration::from_secs(60); -const ADD_PROVIDER_INTERVAL: Duration = Duration::from_secs(60); - diff --git a/protocols/kad/src/protobuf_structs/dht.rs b/protocols/kad/src/protobuf_structs/dht.rs index 9d829a5626e7..97dcf1052d49 100644 --- a/protocols/kad/src/protobuf_structs/dht.rs +++ b/protocols/kad/src/protobuf_structs/dht.rs @@ -27,6 +27,8 @@ pub struct Record { pub key: ::std::vec::Vec, pub value: ::std::vec::Vec, pub timeReceived: ::std::string::String, + pub publisher: ::std::vec::Vec, + pub ttl: u32, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -120,6 +122,47 @@ impl Record { pub fn take_timeReceived(&mut self) -> ::std::string::String { ::std::mem::replace(&mut self.timeReceived, ::std::string::String::new()) } + + // bytes publisher = 666; + + + pub fn get_publisher(&self) -> &[u8] { + &self.publisher + } + pub fn clear_publisher(&mut self) { + self.publisher.clear(); + } + + // Param is passed by value, moved + pub fn set_publisher(&mut self, v: ::std::vec::Vec) { + self.publisher = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_publisher(&mut self) -> &mut ::std::vec::Vec { + &mut self.publisher + } + + // Take field + pub fn take_publisher(&mut self) -> ::std::vec::Vec { + ::std::mem::replace(&mut self.publisher, ::std::vec::Vec::new()) + } + + // uint32 ttl = 777; + + + pub fn get_ttl(&self) -> u32 { + self.ttl + } + pub fn clear_ttl(&mut self) { + self.ttl = 0; + } + + // Param is passed by value, moved + pub fn set_ttl(&mut self, v: u32) { + self.ttl = v; + } } impl ::protobuf::Message for Record { @@ -140,6 +183,16 @@ impl ::protobuf::Message for Record { 5 => { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.timeReceived)?; }, + 666 => { + ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.publisher)?; + }, + 777 => { + if wire_type != ::protobuf::wire_format::WireTypeVarint { + return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); + } + let tmp = is.read_uint32()?; + self.ttl = tmp; + }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; }, @@ -161,6 +214,12 @@ impl ::protobuf::Message for Record { if !self.timeReceived.is_empty() { my_size += ::protobuf::rt::string_size(5, &self.timeReceived); } + if !self.publisher.is_empty() { + my_size += ::protobuf::rt::bytes_size(666, &self.publisher); + } + if self.ttl != 0 { + my_size += ::protobuf::rt::value_size(777, self.ttl, ::protobuf::wire_format::WireTypeVarint); + } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); my_size @@ -176,6 +235,12 @@ impl ::protobuf::Message for Record { if !self.timeReceived.is_empty() { os.write_string(5, &self.timeReceived)?; } + if !self.publisher.is_empty() { + os.write_bytes(666, &self.publisher)?; + } + if self.ttl != 0 { + os.write_uint32(777, self.ttl)?; + } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) } @@ -233,6 +298,16 @@ impl ::protobuf::Message for Record { |m: &Record| { &m.timeReceived }, |m: &mut Record| { &mut m.timeReceived }, )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( + "publisher", + |m: &Record| { &m.publisher }, + |m: &mut Record| { &mut m.publisher }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>( + "ttl", + |m: &Record| { &m.ttl }, + |m: &mut Record| { &mut m.ttl }, + )); ::protobuf::reflect::MessageDescriptor::new::( "Record", fields, @@ -258,6 +333,8 @@ impl ::protobuf::Clear for Record { self.key.clear(); self.value.clear(); self.timeReceived.clear(); + self.publisher.clear(); + self.ttl = 0; self.unknown_fields.clear(); } } @@ -1034,123 +1111,134 @@ impl ::protobuf::reflect::ProtobufValue for Message_ConnectionType { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\tdht.proto\x12\x06dht.pb\"T\n\x06Record\x12\x10\n\x03key\x18\x01\x20\ - \x01(\x0cR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\x05value\x12\ - \"\n\x0ctimeReceived\x18\x05\x20\x01(\tR\x0ctimeReceived\"\xc4\x04\n\x07\ - Message\x12/\n\x04type\x18\x01\x20\x01(\x0e2\x1b.dht.pb.Message.MessageT\ - ypeR\x04type\x12(\n\x0fclusterLevelRaw\x18\n\x20\x01(\x05R\x0fclusterLev\ - elRaw\x12\x10\n\x03key\x18\x02\x20\x01(\x0cR\x03key\x12&\n\x06record\x18\ - \x03\x20\x01(\x0b2\x0e.dht.pb.RecordR\x06record\x126\n\x0bcloserPeers\ - \x18\x08\x20\x03(\x0b2\x14.dht.pb.Message.PeerR\x0bcloserPeers\x12:\n\rp\ - roviderPeers\x18\t\x20\x03(\x0b2\x14.dht.pb.Message.PeerR\rproviderPeers\ - \x1al\n\x04Peer\x12\x0e\n\x02id\x18\x01\x20\x01(\x0cR\x02id\x12\x14\n\ - \x05addrs\x18\x02\x20\x03(\x0cR\x05addrs\x12>\n\nconnection\x18\x03\x20\ - \x01(\x0e2\x1e.dht.pb.Message.ConnectionTypeR\nconnection\"i\n\x0bMessag\ - eType\x12\r\n\tPUT_VALUE\x10\0\x12\r\n\tGET_VALUE\x10\x01\x12\x10\n\x0cA\ - DD_PROVIDER\x10\x02\x12\x11\n\rGET_PROVIDERS\x10\x03\x12\r\n\tFIND_NODE\ - \x10\x04\x12\x08\n\x04PING\x10\x05\"W\n\x0eConnectionType\x12\x11\n\rNOT\ - _CONNECTED\x10\0\x12\r\n\tCONNECTED\x10\x01\x12\x0f\n\x0bCAN_CONNECT\x10\ - \x02\x12\x12\n\x0eCANNOT_CONNECT\x10\x03J\x91\x16\n\x06\x12\x04\0\0P\x01\ - \n\x08\n\x01\x0c\x12\x03\0\0\x12\n\x08\n\x01\x02\x12\x03\x01\x08\x0e\nX\ - \n\x02\x04\0\x12\x04\x05\0\x14\x01\x1aL\x20Record\x20represents\x20a\x20\ - dht\x20record\x20that\x20contains\x20a\x20value\n\x20for\x20a\x20key\x20\ - value\x20pair\n\n\n\n\x03\x04\0\x01\x12\x03\x05\x08\x0e\n2\n\x04\x04\0\ - \x02\0\x12\x03\x07\x08\x16\x1a%\x20The\x20key\x20that\x20references\x20t\ - his\x20record\n\n\r\n\x05\x04\0\x02\0\x04\x12\x04\x07\x08\x05\x10\n\x0c\ - \n\x05\x04\0\x02\0\x05\x12\x03\x07\x08\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\ - \x03\x07\x0e\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x07\x14\x15\n6\n\ - \x04\x04\0\x02\x01\x12\x03\n\x08\x18\x1a)\x20The\x20actual\x20value\x20t\ - his\x20record\x20is\x20storing\n\n\r\n\x05\x04\0\x02\x01\x04\x12\x04\n\ - \x08\x07\x16\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\n\x08\r\n\x0c\n\x05\ - \x04\0\x02\x01\x01\x12\x03\n\x0e\x13\n\x0c\n\x05\x04\0\x02\x01\x03\x12\ - \x03\n\x16\x17\n\xfc\x01\n\x04\x04\0\x02\x02\x12\x03\x13\x08\x20\x1a/\ - \x20Time\x20the\x20record\x20was\x20received,\x20set\x20by\x20receiver\n\ - 2\xbd\x01\x20Note:\x20These\x20fields\x20were\x20removed\x20from\x20the\ - \x20Record\x20message\n\x20hash\x20of\x20the\x20authors\x20public\x20key\ - \noptional\x20string\x20author\x20=\x203;\n\x20A\x20PKI\x20signature\x20\ - for\x20the\x20key+value+author\noptional\x20bytes\x20signature\x20=\x204\ - ;\n\n\r\n\x05\x04\0\x02\x02\x04\x12\x04\x13\x08\n\x18\n\x0c\n\x05\x04\0\ - \x02\x02\x05\x12\x03\x13\x08\x0e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\ - \x13\x0f\x1b\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x13\x1e\x1f\n\n\n\x02\ - \x04\x01\x12\x04\x16\0P\x01\n\n\n\x03\x04\x01\x01\x12\x03\x16\x08\x0f\n\ - \x0c\n\x04\x04\x01\x04\0\x12\x04\x17\x08\x1e\t\n\x0c\n\x05\x04\x01\x04\0\ - \x01\x12\x03\x17\r\x18\n\r\n\x06\x04\x01\x04\0\x02\0\x12\x03\x18\x10\x1e\ - \n\x0e\n\x07\x04\x01\x04\0\x02\0\x01\x12\x03\x18\x10\x19\n\x0e\n\x07\x04\ - \x01\x04\0\x02\0\x02\x12\x03\x18\x1c\x1d\n\r\n\x06\x04\x01\x04\0\x02\x01\ - \x12\x03\x19\x10\x1e\n\x0e\n\x07\x04\x01\x04\0\x02\x01\x01\x12\x03\x19\ - \x10\x19\n\x0e\n\x07\x04\x01\x04\0\x02\x01\x02\x12\x03\x19\x1c\x1d\n\r\n\ - \x06\x04\x01\x04\0\x02\x02\x12\x03\x1a\x10!\n\x0e\n\x07\x04\x01\x04\0\ - \x02\x02\x01\x12\x03\x1a\x10\x1c\n\x0e\n\x07\x04\x01\x04\0\x02\x02\x02\ - \x12\x03\x1a\x1f\x20\n\r\n\x06\x04\x01\x04\0\x02\x03\x12\x03\x1b\x10\"\n\ - \x0e\n\x07\x04\x01\x04\0\x02\x03\x01\x12\x03\x1b\x10\x1d\n\x0e\n\x07\x04\ - \x01\x04\0\x02\x03\x02\x12\x03\x1b\x20!\n\r\n\x06\x04\x01\x04\0\x02\x04\ - \x12\x03\x1c\x10\x1e\n\x0e\n\x07\x04\x01\x04\0\x02\x04\x01\x12\x03\x1c\ - \x10\x19\n\x0e\n\x07\x04\x01\x04\0\x02\x04\x02\x12\x03\x1c\x1c\x1d\n\r\n\ - \x06\x04\x01\x04\0\x02\x05\x12\x03\x1d\x10\x19\n\x0e\n\x07\x04\x01\x04\0\ - \x02\x05\x01\x12\x03\x1d\x10\x14\n\x0e\n\x07\x04\x01\x04\0\x02\x05\x02\ - \x12\x03\x1d\x17\x18\n\x0c\n\x04\x04\x01\x04\x01\x12\x04\x20\x08-\t\n\ - \x0c\n\x05\x04\x01\x04\x01\x01\x12\x03\x20\r\x1b\n^\n\x06\x04\x01\x04\ - \x01\x02\0\x12\x03\"\x10\"\x1aO\x20sender\x20does\x20not\x20have\x20a\ - \x20connection\x20to\x20peer,\x20and\x20no\x20extra\x20information\x20(d\ - efault)\n\n\x0e\n\x07\x04\x01\x04\x01\x02\0\x01\x12\x03\"\x10\x1d\n\x0e\ - \n\x07\x04\x01\x04\x01\x02\0\x02\x12\x03\"\x20!\n5\n\x06\x04\x01\x04\x01\ - \x02\x01\x12\x03%\x10\x1e\x1a&\x20sender\x20has\x20a\x20live\x20connecti\ - on\x20to\x20peer\n\n\x0e\n\x07\x04\x01\x04\x01\x02\x01\x01\x12\x03%\x10\ - \x19\n\x0e\n\x07\x04\x01\x04\x01\x02\x01\x02\x12\x03%\x1c\x1d\n2\n\x06\ - \x04\x01\x04\x01\x02\x02\x12\x03(\x10\x20\x1a#\x20sender\x20recently\x20\ - connected\x20to\x20peer\n\n\x0e\n\x07\x04\x01\x04\x01\x02\x02\x01\x12\ - \x03(\x10\x1b\n\x0e\n\x07\x04\x01\x04\x01\x02\x02\x02\x12\x03(\x1e\x1f\n\ - \xa7\x01\n\x06\x04\x01\x04\x01\x02\x03\x12\x03,\x10#\x1a\x97\x01\x20send\ - er\x20recently\x20tried\x20to\x20connect\x20to\x20peer\x20repeatedly\x20\ - but\x20failed\x20to\x20connect\n\x20(\"try\"\x20here\x20is\x20loose,\x20\ - but\x20this\x20should\x20signal\x20\"made\x20strong\x20effort,\x20failed\ - \")\n\n\x0e\n\x07\x04\x01\x04\x01\x02\x03\x01\x12\x03,\x10\x1e\n\x0e\n\ - \x07\x04\x01\x04\x01\x02\x03\x02\x12\x03,!\"\n\x0c\n\x04\x04\x01\x03\0\ - \x12\x04/\x088\t\n\x0c\n\x05\x04\x01\x03\0\x01\x12\x03/\x10\x14\n$\n\x06\ - \x04\x01\x03\0\x02\0\x12\x031\x10\x1d\x1a\x15\x20ID\x20of\x20a\x20given\ - \x20peer.\n\n\x0f\n\x07\x04\x01\x03\0\x02\0\x04\x12\x041\x10/\x16\n\x0e\ - \n\x07\x04\x01\x03\0\x02\0\x05\x12\x031\x10\x15\n\x0e\n\x07\x04\x01\x03\ - \0\x02\0\x01\x12\x031\x16\x18\n\x0e\n\x07\x04\x01\x03\0\x02\0\x03\x12\ - \x031\x1b\x1c\n,\n\x06\x04\x01\x03\0\x02\x01\x12\x034\x10)\x1a\x1d\x20mu\ - ltiaddrs\x20for\x20a\x20given\x20peer\n\n\x0e\n\x07\x04\x01\x03\0\x02\ - \x01\x04\x12\x034\x10\x18\n\x0e\n\x07\x04\x01\x03\0\x02\x01\x05\x12\x034\ - \x19\x1e\n\x0e\n\x07\x04\x01\x03\0\x02\x01\x01\x12\x034\x1f$\n\x0e\n\x07\ - \x04\x01\x03\0\x02\x01\x03\x12\x034'(\nP\n\x06\x04\x01\x03\0\x02\x02\x12\ - \x037\x10.\x1aA\x20used\x20to\x20signal\x20the\x20sender's\x20connection\ - \x20capabilities\x20to\x20the\x20peer\n\n\x0f\n\x07\x04\x01\x03\0\x02\ - \x02\x04\x12\x047\x104)\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x06\x12\x037\ - \x10\x1e\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x01\x12\x037\x1f)\n\x0e\n\x07\ - \x04\x01\x03\0\x02\x02\x03\x12\x037,-\n2\n\x04\x04\x01\x02\0\x12\x03;\ - \x08\x1d\x1a%\x20defines\x20what\x20type\x20of\x20message\x20it\x20is.\n\ - \n\r\n\x05\x04\x01\x02\0\x04\x12\x04;\x088\t\n\x0c\n\x05\x04\x01\x02\0\ - \x06\x12\x03;\x08\x13\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03;\x14\x18\n\ - \x0c\n\x05\x04\x01\x02\0\x03\x12\x03;\x1b\x1c\n\x9f\x01\n\x04\x04\x01\ - \x02\x01\x12\x03?\x08#\x1a\x85\x01\x20defines\x20what\x20coral\x20cluste\ - r\x20level\x20this\x20query/response\x20belongs\x20to.\n\x20in\x20case\ - \x20we\x20want\x20to\x20implement\x20coral's\x20cluster\x20rings\x20in\ + \n\tdht.proto\x12\x06dht.pb\"\x86\x01\n\x06Record\x12\x10\n\x03key\x18\ + \x01\x20\x01(\x0cR\x03key\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\x05va\ + lue\x12\"\n\x0ctimeReceived\x18\x05\x20\x01(\tR\x0ctimeReceived\x12\x1d\ + \n\tpublisher\x18\x9a\x05\x20\x01(\x0cR\tpublisher\x12\x11\n\x03ttl\x18\ + \x89\x06\x20\x01(\rR\x03ttl\"\xc4\x04\n\x07Message\x12/\n\x04type\x18\ + \x01\x20\x01(\x0e2\x1b.dht.pb.Message.MessageTypeR\x04type\x12(\n\x0fclu\ + sterLevelRaw\x18\n\x20\x01(\x05R\x0fclusterLevelRaw\x12\x10\n\x03key\x18\ + \x02\x20\x01(\x0cR\x03key\x12&\n\x06record\x18\x03\x20\x01(\x0b2\x0e.dht\ + .pb.RecordR\x06record\x126\n\x0bcloserPeers\x18\x08\x20\x03(\x0b2\x14.dh\ + t.pb.Message.PeerR\x0bcloserPeers\x12:\n\rproviderPeers\x18\t\x20\x03(\ + \x0b2\x14.dht.pb.Message.PeerR\rproviderPeers\x1al\n\x04Peer\x12\x0e\n\ + \x02id\x18\x01\x20\x01(\x0cR\x02id\x12\x14\n\x05addrs\x18\x02\x20\x03(\ + \x0cR\x05addrs\x12>\n\nconnection\x18\x03\x20\x01(\x0e2\x1e.dht.pb.Messa\ + ge.ConnectionTypeR\nconnection\"i\n\x0bMessageType\x12\r\n\tPUT_VALUE\ + \x10\0\x12\r\n\tGET_VALUE\x10\x01\x12\x10\n\x0cADD_PROVIDER\x10\x02\x12\ + \x11\n\rGET_PROVIDERS\x10\x03\x12\r\n\tFIND_NODE\x10\x04\x12\x08\n\x04PI\ + NG\x10\x05\"W\n\x0eConnectionType\x12\x11\n\rNOT_CONNECTED\x10\0\x12\r\n\ + \tCONNECTED\x10\x01\x12\x0f\n\x0bCAN_CONNECT\x10\x02\x12\x12\n\x0eCANNOT\ + _CONNECT\x10\x03J\xbe\x18\n\x06\x12\x04\0\0X\x01\n\x08\n\x01\x0c\x12\x03\ + \0\0\x12\n\x08\n\x01\x02\x12\x03\x01\x08\x0e\nX\n\x02\x04\0\x12\x04\x05\ + \0\x1c\x01\x1aL\x20Record\x20represents\x20a\x20dht\x20record\x20that\ + \x20contains\x20a\x20value\n\x20for\x20a\x20key\x20value\x20pair\n\n\n\n\ + \x03\x04\0\x01\x12\x03\x05\x08\x0e\n2\n\x04\x04\0\x02\0\x12\x03\x07\x08\ + \x16\x1a%\x20The\x20key\x20that\x20references\x20this\x20record\n\n\r\n\ + \x05\x04\0\x02\0\x04\x12\x04\x07\x08\x05\x10\n\x0c\n\x05\x04\0\x02\0\x05\ + \x12\x03\x07\x08\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x07\x0e\x11\n\x0c\ + \n\x05\x04\0\x02\0\x03\x12\x03\x07\x14\x15\n6\n\x04\x04\0\x02\x01\x12\ + \x03\n\x08\x18\x1a)\x20The\x20actual\x20value\x20this\x20record\x20is\ + \x20storing\n\n\r\n\x05\x04\0\x02\x01\x04\x12\x04\n\x08\x07\x16\n\x0c\n\ + \x05\x04\0\x02\x01\x05\x12\x03\n\x08\r\n\x0c\n\x05\x04\0\x02\x01\x01\x12\ + \x03\n\x0e\x13\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\n\x16\x17\n\xfc\x01\ + \n\x04\x04\0\x02\x02\x12\x03\x13\x08\x20\x1a/\x20Time\x20the\x20record\ + \x20was\x20received,\x20set\x20by\x20receiver\n2\xbd\x01\x20Note:\x20The\ + se\x20fields\x20were\x20removed\x20from\x20the\x20Record\x20message\n\ + \x20hash\x20of\x20the\x20authors\x20public\x20key\noptional\x20string\ + \x20author\x20=\x203;\n\x20A\x20PKI\x20signature\x20for\x20the\x20key+va\ + lue+author\noptional\x20bytes\x20signature\x20=\x204;\n\n\r\n\x05\x04\0\ + \x02\x02\x04\x12\x04\x13\x08\n\x18\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\ + \x13\x08\x0e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x13\x0f\x1b\n\x0c\n\ + \x05\x04\0\x02\x02\x03\x12\x03\x13\x1e\x1f\nX\n\x04\x04\0\x02\x03\x12\ + \x03\x17\x04\x1a\x1aK\x20The\x20original\x20publisher\x20of\x20the\x20re\ + cord.\n\x20Currently\x20specific\x20to\x20rust-libp2p.\n\n\r\n\x05\x04\0\ + \x02\x03\x04\x12\x04\x17\x04\x13\x20\n\x0c\n\x05\x04\0\x02\x03\x05\x12\ + \x03\x17\x04\t\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x17\n\x13\n\x0c\n\ + \x05\x04\0\x02\x03\x03\x12\x03\x17\x16\x19\n_\n\x04\x04\0\x02\x04\x12\ + \x03\x1b\x04\x15\x1aR\x20The\x20remaining\x20TTL\x20of\x20the\x20record,\ + \x20in\x20seconds.\n\x20Currently\x20specific\x20to\x20rust-libp2p.\n\n\ + \r\n\x05\x04\0\x02\x04\x04\x12\x04\x1b\x04\x17\x1a\n\x0c\n\x05\x04\0\x02\ + \x04\x05\x12\x03\x1b\x04\n\n\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x1b\x0b\ + \x0e\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x1b\x11\x14\n\n\n\x02\x04\x01\ + \x12\x04\x1e\0X\x01\n\n\n\x03\x04\x01\x01\x12\x03\x1e\x08\x0f\n\x0c\n\ + \x04\x04\x01\x04\0\x12\x04\x1f\x08&\t\n\x0c\n\x05\x04\x01\x04\0\x01\x12\ + \x03\x1f\r\x18\n\r\n\x06\x04\x01\x04\0\x02\0\x12\x03\x20\x10\x1e\n\x0e\n\ + \x07\x04\x01\x04\0\x02\0\x01\x12\x03\x20\x10\x19\n\x0e\n\x07\x04\x01\x04\ + \0\x02\0\x02\x12\x03\x20\x1c\x1d\n\r\n\x06\x04\x01\x04\0\x02\x01\x12\x03\ + !\x10\x1e\n\x0e\n\x07\x04\x01\x04\0\x02\x01\x01\x12\x03!\x10\x19\n\x0e\n\ + \x07\x04\x01\x04\0\x02\x01\x02\x12\x03!\x1c\x1d\n\r\n\x06\x04\x01\x04\0\ + \x02\x02\x12\x03\"\x10!\n\x0e\n\x07\x04\x01\x04\0\x02\x02\x01\x12\x03\"\ + \x10\x1c\n\x0e\n\x07\x04\x01\x04\0\x02\x02\x02\x12\x03\"\x1f\x20\n\r\n\ + \x06\x04\x01\x04\0\x02\x03\x12\x03#\x10\"\n\x0e\n\x07\x04\x01\x04\0\x02\ + \x03\x01\x12\x03#\x10\x1d\n\x0e\n\x07\x04\x01\x04\0\x02\x03\x02\x12\x03#\ + \x20!\n\r\n\x06\x04\x01\x04\0\x02\x04\x12\x03$\x10\x1e\n\x0e\n\x07\x04\ + \x01\x04\0\x02\x04\x01\x12\x03$\x10\x19\n\x0e\n\x07\x04\x01\x04\0\x02\ + \x04\x02\x12\x03$\x1c\x1d\n\r\n\x06\x04\x01\x04\0\x02\x05\x12\x03%\x10\ + \x19\n\x0e\n\x07\x04\x01\x04\0\x02\x05\x01\x12\x03%\x10\x14\n\x0e\n\x07\ + \x04\x01\x04\0\x02\x05\x02\x12\x03%\x17\x18\n\x0c\n\x04\x04\x01\x04\x01\ + \x12\x04(\x085\t\n\x0c\n\x05\x04\x01\x04\x01\x01\x12\x03(\r\x1b\n^\n\x06\ + \x04\x01\x04\x01\x02\0\x12\x03*\x10\"\x1aO\x20sender\x20does\x20not\x20h\ + ave\x20a\x20connection\x20to\x20peer,\x20and\x20no\x20extra\x20informati\ + on\x20(default)\n\n\x0e\n\x07\x04\x01\x04\x01\x02\0\x01\x12\x03*\x10\x1d\ + \n\x0e\n\x07\x04\x01\x04\x01\x02\0\x02\x12\x03*\x20!\n5\n\x06\x04\x01\ + \x04\x01\x02\x01\x12\x03-\x10\x1e\x1a&\x20sender\x20has\x20a\x20live\x20\ + connection\x20to\x20peer\n\n\x0e\n\x07\x04\x01\x04\x01\x02\x01\x01\x12\ + \x03-\x10\x19\n\x0e\n\x07\x04\x01\x04\x01\x02\x01\x02\x12\x03-\x1c\x1d\n\ + 2\n\x06\x04\x01\x04\x01\x02\x02\x12\x030\x10\x20\x1a#\x20sender\x20recen\ + tly\x20connected\x20to\x20peer\n\n\x0e\n\x07\x04\x01\x04\x01\x02\x02\x01\ + \x12\x030\x10\x1b\n\x0e\n\x07\x04\x01\x04\x01\x02\x02\x02\x12\x030\x1e\ + \x1f\n\xa7\x01\n\x06\x04\x01\x04\x01\x02\x03\x12\x034\x10#\x1a\x97\x01\ + \x20sender\x20recently\x20tried\x20to\x20connect\x20to\x20peer\x20repeat\ + edly\x20but\x20failed\x20to\x20connect\n\x20(\"try\"\x20here\x20is\x20lo\ + ose,\x20but\x20this\x20should\x20signal\x20\"made\x20strong\x20effort,\ + \x20failed\")\n\n\x0e\n\x07\x04\x01\x04\x01\x02\x03\x01\x12\x034\x10\x1e\ + \n\x0e\n\x07\x04\x01\x04\x01\x02\x03\x02\x12\x034!\"\n\x0c\n\x04\x04\x01\ + \x03\0\x12\x047\x08@\t\n\x0c\n\x05\x04\x01\x03\0\x01\x12\x037\x10\x14\n$\ + \n\x06\x04\x01\x03\0\x02\0\x12\x039\x10\x1d\x1a\x15\x20ID\x20of\x20a\x20\ + given\x20peer.\n\n\x0f\n\x07\x04\x01\x03\0\x02\0\x04\x12\x049\x107\x16\n\ + \x0e\n\x07\x04\x01\x03\0\x02\0\x05\x12\x039\x10\x15\n\x0e\n\x07\x04\x01\ + \x03\0\x02\0\x01\x12\x039\x16\x18\n\x0e\n\x07\x04\x01\x03\0\x02\0\x03\ + \x12\x039\x1b\x1c\n,\n\x06\x04\x01\x03\0\x02\x01\x12\x03<\x10)\x1a\x1d\ + \x20multiaddrs\x20for\x20a\x20given\x20peer\n\n\x0e\n\x07\x04\x01\x03\0\ + \x02\x01\x04\x12\x03<\x10\x18\n\x0e\n\x07\x04\x01\x03\0\x02\x01\x05\x12\ + \x03<\x19\x1e\n\x0e\n\x07\x04\x01\x03\0\x02\x01\x01\x12\x03<\x1f$\n\x0e\ + \n\x07\x04\x01\x03\0\x02\x01\x03\x12\x03<'(\nP\n\x06\x04\x01\x03\0\x02\ + \x02\x12\x03?\x10.\x1aA\x20used\x20to\x20signal\x20the\x20sender's\x20co\ + nnection\x20capabilities\x20to\x20the\x20peer\n\n\x0f\n\x07\x04\x01\x03\ + \0\x02\x02\x04\x12\x04?\x10<)\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x06\x12\ + \x03?\x10\x1e\n\x0e\n\x07\x04\x01\x03\0\x02\x02\x01\x12\x03?\x1f)\n\x0e\ + \n\x07\x04\x01\x03\0\x02\x02\x03\x12\x03?,-\n2\n\x04\x04\x01\x02\0\x12\ + \x03C\x08\x1d\x1a%\x20defines\x20what\x20type\x20of\x20message\x20it\x20\ + is.\n\n\r\n\x05\x04\x01\x02\0\x04\x12\x04C\x08@\t\n\x0c\n\x05\x04\x01\ + \x02\0\x06\x12\x03C\x08\x13\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03C\x14\ + \x18\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03C\x1b\x1c\n\x9f\x01\n\x04\x04\ + \x01\x02\x01\x12\x03G\x08#\x1a\x85\x01\x20defines\x20what\x20coral\x20cl\ + uster\x20level\x20this\x20query/response\x20belongs\x20to.\n\x20in\x20ca\ + se\x20we\x20want\x20to\x20implement\x20coral's\x20cluster\x20rings\x20in\ \x20the\x20future.\n\"\n\x20NOT\x20USED\n\n\r\n\x05\x04\x01\x02\x01\x04\ - \x12\x04?\x08;\x1d\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03?\x08\r\n\x0c\ - \n\x05\x04\x01\x02\x01\x01\x12\x03?\x0e\x1d\n\x0c\n\x05\x04\x01\x02\x01\ - \x03\x12\x03?\x20\"\nw\n\x04\x04\x01\x02\x02\x12\x03C\x08\x16\x1aj\x20Us\ + \x12\x04G\x08C\x1d\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03G\x08\r\n\x0c\ + \n\x05\x04\x01\x02\x01\x01\x12\x03G\x0e\x1d\n\x0c\n\x05\x04\x01\x02\x01\ + \x03\x12\x03G\x20\"\nw\n\x04\x04\x01\x02\x02\x12\x03K\x08\x16\x1aj\x20Us\ ed\x20to\x20specify\x20the\x20key\x20associated\x20with\x20this\x20messa\ ge.\n\x20PUT_VALUE,\x20GET_VALUE,\x20ADD_PROVIDER,\x20GET_PROVIDERS\n\n\ - \r\n\x05\x04\x01\x02\x02\x04\x12\x04C\x08?#\n\x0c\n\x05\x04\x01\x02\x02\ - \x05\x12\x03C\x08\r\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03C\x0e\x11\n\ - \x0c\n\x05\x04\x01\x02\x02\x03\x12\x03C\x14\x15\n;\n\x04\x04\x01\x02\x03\ - \x12\x03G\x08\x1a\x1a.\x20Used\x20to\x20return\x20a\x20value\n\x20PUT_VA\ - LUE,\x20GET_VALUE\n\n\r\n\x05\x04\x01\x02\x03\x04\x12\x04G\x08C\x16\n\ - \x0c\n\x05\x04\x01\x02\x03\x06\x12\x03G\x08\x0e\n\x0c\n\x05\x04\x01\x02\ - \x03\x01\x12\x03G\x0f\x15\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03G\x18\ - \x19\nc\n\x04\x04\x01\x02\x04\x12\x03K\x08&\x1aV\x20Used\x20to\x20return\ + \r\n\x05\x04\x01\x02\x02\x04\x12\x04K\x08G#\n\x0c\n\x05\x04\x01\x02\x02\ + \x05\x12\x03K\x08\r\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03K\x0e\x11\n\ + \x0c\n\x05\x04\x01\x02\x02\x03\x12\x03K\x14\x15\n;\n\x04\x04\x01\x02\x03\ + \x12\x03O\x08\x1a\x1a.\x20Used\x20to\x20return\x20a\x20value\n\x20PUT_VA\ + LUE,\x20GET_VALUE\n\n\r\n\x05\x04\x01\x02\x03\x04\x12\x04O\x08K\x16\n\ + \x0c\n\x05\x04\x01\x02\x03\x06\x12\x03O\x08\x0e\n\x0c\n\x05\x04\x01\x02\ + \x03\x01\x12\x03O\x0f\x15\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03O\x18\ + \x19\nc\n\x04\x04\x01\x02\x04\x12\x03S\x08&\x1aV\x20Used\x20to\x20return\ \x20peers\x20closer\x20to\x20a\x20key\x20in\x20a\x20query\n\x20GET_VALUE\ ,\x20GET_PROVIDERS,\x20FIND_NODE\n\n\x0c\n\x05\x04\x01\x02\x04\x04\x12\ - \x03K\x08\x10\n\x0c\n\x05\x04\x01\x02\x04\x06\x12\x03K\x11\x15\n\x0c\n\ - \x05\x04\x01\x02\x04\x01\x12\x03K\x16!\n\x0c\n\x05\x04\x01\x02\x04\x03\ - \x12\x03K$%\nO\n\x04\x04\x01\x02\x05\x12\x03O\x08(\x1aB\x20Used\x20to\ + \x03S\x08\x10\n\x0c\n\x05\x04\x01\x02\x04\x06\x12\x03S\x11\x15\n\x0c\n\ + \x05\x04\x01\x02\x04\x01\x12\x03S\x16!\n\x0c\n\x05\x04\x01\x02\x04\x03\ + \x12\x03S$%\nO\n\x04\x04\x01\x02\x05\x12\x03W\x08(\x1aB\x20Used\x20to\ \x20return\x20Providers\n\x20GET_VALUE,\x20ADD_PROVIDER,\x20GET_PROVIDER\ - S\n\n\x0c\n\x05\x04\x01\x02\x05\x04\x12\x03O\x08\x10\n\x0c\n\x05\x04\x01\ - \x02\x05\x06\x12\x03O\x11\x15\n\x0c\n\x05\x04\x01\x02\x05\x01\x12\x03O\ - \x16#\n\x0c\n\x05\x04\x01\x02\x05\x03\x12\x03O&'b\x06proto3\ + S\n\n\x0c\n\x05\x04\x01\x02\x05\x04\x12\x03W\x08\x10\n\x0c\n\x05\x04\x01\ + \x02\x05\x06\x12\x03W\x11\x15\n\x0c\n\x05\x04\x01\x02\x05\x01\x12\x03W\ + \x16#\n\x0c\n\x05\x04\x01\x02\x05\x03\x12\x03W&'b\x06proto3\ "; static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy { diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index 37a9bc387400..f9631c268f00 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -39,7 +39,7 @@ use libp2p_core::{Multiaddr, PeerId}; use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo, Negotiated}; use multihash::Multihash; use protobuf::{self, Message}; -use std::{borrow::Cow, convert::TryFrom}; +use std::{borrow::Cow, convert::TryFrom, time::{Duration, Instant}}; use std::{io, iter}; use tokio_codec::Framed; use tokio_io::{AsyncRead, AsyncWrite}; @@ -272,7 +272,7 @@ pub enum KadRequestMsg { /// Key for which we should add providers. key: Multihash, /// Known provider for this key. - provider_peer: KadPeer, + provider: KadPeer, }, /// Request to get a value from the dht records. @@ -283,10 +283,7 @@ pub enum KadRequestMsg { /// Request to put a value into the dht records. PutValue { - /// The key of the record. - key: Multihash, - /// The value of the record. - value: Vec, + record: Record, } } @@ -313,7 +310,7 @@ pub enum KadResponseMsg { /// Response to a `GetValue`. GetValue { /// Result that might have been found - result: Option, + record: Option, /// Nodes closest to the key closer_peers: Vec, }, @@ -349,12 +346,12 @@ fn req_msg_to_proto(kad_msg: KadRequestMsg) -> proto::Message { msg.set_clusterLevelRaw(10); msg } - KadRequestMsg::AddProvider { key, provider_peer } => { + KadRequestMsg::AddProvider { key, provider } => { let mut msg = proto::Message::new(); msg.set_field_type(proto::Message_MessageType::ADD_PROVIDER); msg.set_clusterLevelRaw(10); msg.set_key(key.into_bytes()); - msg.mut_providerPeers().push(provider_peer.into()); + msg.mut_providerPeers().push(provider.into()); msg } KadRequestMsg::GetValue { key } => { @@ -365,14 +362,10 @@ fn req_msg_to_proto(kad_msg: KadRequestMsg) -> proto::Message { msg } - KadRequestMsg::PutValue { key, value} => { + KadRequestMsg::PutValue { record } => { let mut msg = proto::Message::new(); msg.set_field_type(proto::Message_MessageType::PUT_VALUE); - let mut record = proto::Record::new(); - record.set_value(value); - record.set_key(key.into_bytes()); - - msg.set_record(record); + msg.set_record(record_to_proto(record)); msg } } @@ -411,7 +404,7 @@ fn resp_msg_to_proto(kad_msg: KadResponseMsg) -> proto::Message { msg } KadResponseMsg::GetValue { - result, + record, closer_peers, } => { let mut msg = proto::Message::new(); @@ -420,12 +413,8 @@ fn resp_msg_to_proto(kad_msg: KadResponseMsg) -> proto::Message { for peer in closer_peers { msg.mut_closerPeers().push(peer.into()); } - - if let Some(Record{ key, value }) = result { - let mut record = proto::Record::new(); - record.set_key(key.into_bytes()); - record.set_value(value); - msg.set_record(record); + if let Some(record) = record { + msg.set_record(record_to_proto(record)); } msg @@ -456,9 +445,8 @@ fn proto_to_req_msg(mut message: proto::Message) -> Result Ok(KadRequestMsg::Ping), proto::Message_MessageType::PUT_VALUE => { - let record = message.mut_record(); - let key = Multihash::from_bytes(record.take_key()).map_err(invalid_data)?; - Ok(KadRequestMsg::PutValue { key, value: record.take_value() }) + let record = record_from_proto(message.take_record())?; + Ok(KadRequestMsg::PutValue { record }) } proto::Message_MessageType::GET_VALUE => { @@ -481,14 +469,14 @@ fn proto_to_req_msg(mut message: proto::Message) -> Result Result Ok(KadResponseMsg::Pong), proto::Message_MessageType::GET_VALUE => { - let result = match message.has_record() { - true => { - let mut record = message.take_record(); - let key = Multihash::from_bytes(record.take_key()).map_err(invalid_data)?; - Some(Record { key, value: record.take_value() }) - } - false => None, - }; + let record = + if message.has_record() { + Some(record_from_proto(message.take_record())?) + } else { + None + }; let closer_peers = message .mut_closerPeers() @@ -519,7 +505,7 @@ fn proto_to_resp_msg(mut message: proto::Message) -> Result>(); - Ok(KadResponseMsg::GetValue { result, closer_peers }) + Ok(KadResponseMsg::GetValue { record, closer_peers }) }, proto::Message_MessageType::FIND_NODE => { @@ -569,6 +555,48 @@ fn proto_to_resp_msg(mut message: proto::Message) -> Result Result { + let key = Multihash::from_bytes(record.take_key()).map_err(invalid_data)?; + let value = record.take_value(); + + let publisher = + if record.publisher.len() > 0 { + PeerId::from_bytes(record.take_publisher()) + .map(Some) + .map_err(|_| invalid_data("Invalid publisher peer ID."))? + } else { + None + }; + + let expires = + if record.ttl > 0 { + Some(Instant::now() + Duration::from_secs(record.ttl as u64)) + } else { + None + }; + + Ok(Record { key, value, publisher, expires }) +} + +fn record_to_proto(record: Record) -> proto::Record { + let mut pb_record = proto::Record::new(); + pb_record.key = record.key.into_bytes(); + pb_record.value = record.value; + if let Some(p) = record.publisher { + pb_record.publisher = p.into_bytes(); + } + if let Some(t) = record.expires { + let now = Instant::now(); + if t > now { + pb_record.ttl = (t - now).as_secs() as u32; + } else { + pb_record.ttl = 1; // because 0 means "does not expire" + } + } + + pb_record +} + /// Creates an `io::Error` with `io::ErrorKind::InvalidData`. fn invalid_data(e: E) -> io::Error where diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index 16809db6262e..02213fafd45c 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -26,11 +26,10 @@ use peers::fixed::FixedPeersIter; use crate::K_VALUE; use crate::kbucket::{Key, KeyBytes}; -use crate::handler::KademliaHandlerIn; use either::Either; use fnv::FnvHashMap; use libp2p_core::PeerId; -use std::time::Duration; +use std::{time::Duration, num::NonZeroUsize}; use wasm_timer::Instant; /// A `QueryPool` provides an aggregate state machine for driving `Query`s to completion. @@ -50,7 +49,7 @@ pub enum QueryPoolState<'a, TInner> { Idle, /// At least one query is waiting for results. `Some(request)` indicates /// that a new request is now being waited on. - Waiting(Option<(&'a Query, PeerId)>), + Waiting(Option<(&'a mut Query, PeerId)>), /// A query has finished. Finished(Query), /// A query has timed out. @@ -77,6 +76,11 @@ impl QueryPool { self.queries.values() } + /// Gets the current size of the pool, i.e. the number of running queries. + pub fn size(&self) -> usize { + self.queries.len() + } + /// Returns an iterator that allows modifying each query in the pool. pub fn iter_mut(&mut self) -> impl Iterator> { self.queries.values_mut() @@ -88,7 +92,7 @@ impl QueryPool { I: IntoIterator> { let peers = peers.into_iter().map(|k| k.into_preimage()).collect::>(); - let parallelism = self.config.replication_factor; + let parallelism = self.config.replication_factor.get(); let peer_iter = QueryPeerIter::Fixed(FixedPeersIter::new(peers, parallelism)); self.add(peer_iter, inner) } @@ -100,7 +104,7 @@ impl QueryPool { I: IntoIterator> { let cfg = ClosestPeersIterConfig { - num_results: self.config.replication_factor, + num_results: self.config.replication_factor.get(), .. ClosestPeersIterConfig::default() }; let peer_iter = QueryPeerIter::Closest(ClosestPeersIter::with_config(cfg, target, peers)); @@ -154,7 +158,7 @@ impl QueryPool { } if let Some((query_id, peer_id)) = waiting { - let query = self.queries.get(&query_id).expect("s.a."); + let query = self.queries.get_mut(&query_id).expect("s.a."); return QueryPoolState::Waiting(Some((query, peer_id))) } @@ -181,17 +185,17 @@ impl QueryPool { pub struct QueryId(usize); /// The configuration for queries in a `QueryPool`. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct QueryConfig { pub timeout: Duration, - pub replication_factor: usize, + pub replication_factor: NonZeroUsize, } impl Default for QueryConfig { fn default() -> Self { QueryConfig { timeout: Duration::from_secs(60), - replication_factor: K_VALUE + replication_factor: NonZeroUsize::new(K_VALUE).expect("K_VALUE > 0") } } } diff --git a/protocols/kad/src/record.rs b/protocols/kad/src/record.rs index 611acaa21e2b..6c3c8102574d 100644 --- a/protocols/kad/src/record.rs +++ b/protocols/kad/src/record.rs @@ -20,83 +20,111 @@ //! Records and record storage abstraction of the libp2p Kademlia DHT. -use fnv::FnvHashMap; +pub mod store; + +use libp2p_core::PeerId; use multihash::Multihash; -use std::borrow::Cow; +use std::hash::{Hash, Hasher}; +use std::time::Instant; -/// The records that are kept in the dht. +/// A record stored in the DHT. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Record { /// Key of the record. pub key: Multihash, /// Value of the record. pub value: Vec, + /// The (original) publisher of the record. + pub publisher: Option, + /// The expiration time as measured by a local, monotonic clock. + pub expires: Option, } -/// Trait for a record store. -pub trait RecordStore { - fn get(&self, k: &Multihash) -> Option>; - fn put(&mut self, r: Record) -> Result<(), RecordStorageError>; -} +impl Record { + /// Creates a new record for insertion into the DHT. + pub fn new(key: Multihash, value: Vec) -> Self { + Record { + key, + value, + publisher: None, + expires: None, + } + } -/// The error record store may return -#[derive(Clone, Debug, PartialEq)] -pub enum RecordStorageError { - /// Store reached the capacity limit. - AtCapacity, - /// Value being put is larger than the limit. - ValueTooLarge, + /// Checks whether the record is expired w.r.t. the given `Instant`. + pub fn is_expired(&self, now: Instant) -> bool { + self.expires.map_or(false, |t| now >= t) + } } -/// In-memory implementation of the record store. -pub struct MemoryRecordStorage { - /// Maximum number of records we will store. - max_records: usize, - /// Maximum size of the record we will store. - max_record_size: usize, - /// The records. - records: FnvHashMap +/// A record stored in the DHT whose value is the ID of a peer +/// who can provide the value on-demand. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProviderRecord { + /// The key whose value is provided by the provider. + pub key: Multihash, + /// The provider of the value for the key. + pub provider: PeerId, + /// The expiration time as measured by a local, monotonic clock. + pub expires: Option, } -impl MemoryRecordStorage { - const MAX_RECORDS: usize = 1024; - const MAX_RECORD_SIZE: usize = 65535; +impl Hash for ProviderRecord { + fn hash(&self, state: &mut H) { + self.key.hash(state); + self.provider.hash(state); + } +} - /// Creates a new `MemoryRecordStorage`. - pub fn new(max_records: usize, max_record_size: usize) -> Self { - MemoryRecordStorage{ - max_records, - max_record_size, - records: FnvHashMap::default() +impl ProviderRecord { + /// Creates a new provider record for insertion into a `RecordStore`. + pub fn new(key: Multihash, provider: PeerId) -> Self { + ProviderRecord { + key, provider, expires: None } } -} -impl Default for MemoryRecordStorage { - fn default() -> Self { - MemoryRecordStorage::new(Self::MAX_RECORDS, Self::MAX_RECORD_SIZE) + /// Checks whether the provider record is expired w.r.t. the given `Instant`. + pub fn is_expired(&self, now: Instant) -> bool { + self.expires.map_or(false, |t| now >= t) } } -impl RecordStore for MemoryRecordStorage { - fn get(&self, k: &Multihash) -> Option> { - match self.records.get(k) { - Some(rec) => Some(Cow::Borrowed(rec)), - None => None, - } - } +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::*; + use multihash::Hash::SHA2256; + use rand::Rng; + use std::time::Duration; - fn put(&mut self, r: Record) -> Result<(), RecordStorageError> { - if self.records.len() >= self.max_records { - return Err(RecordStorageError::AtCapacity); + impl Arbitrary for Record { + fn arbitrary(g: &mut G) -> Record { + Record { + key: Multihash::random(SHA2256), + value: Vec::arbitrary(g), + publisher: if g.gen() { Some(PeerId::random()) } else { None }, + expires: if g.gen() { + Some(Instant::now() + Duration::from_secs(g.gen_range(0, 60))) + } else { + None + }, + } } + } - if r.value.len() >= self.max_record_size { - return Err(RecordStorageError::ValueTooLarge) + impl Arbitrary for ProviderRecord { + fn arbitrary(g: &mut G) -> ProviderRecord { + ProviderRecord { + key: Multihash::random(SHA2256), + provider: PeerId::random(), + expires: if g.gen() { + Some(Instant::now() + Duration::from_secs(g.gen_range(0, 60))) + } else { + None + }, + } } - - self.records.insert(r.key.clone(), r); - - Ok(()) } } +