mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-28 01:01:34 +00:00
Kademlia: Address some TODOs - Refactoring - API updates. (#1174)
* Address some TODOs, refactor queries and public API. The following left-over issues are addressed: * The key for FIND_NODE requests is generalised to any Multihash, instead of just peer IDs. * All queries get a (configurable) timeout. * Finishing queries as soon as enough results have been received is simplified to avoid code duplication. * No more panics in provider-API-related code paths. The provider API is however still untested and (I think) still incomplete (e.g. expiration of provider records). * Numerous smaller TODOs encountered in the code. The following public API changes / additions are made: * Introduce a `KademliaConfig` with new configuration options for the replication factor and query timeouts. * Rename `find_node` to `get_closest_peers`. * Rename `get_value` to `get_record` and `put_value` to `put_record`, introducing a `Quorum` parameter for both functions, replacing the existing `num_results` parameter with clearer semantics. * Rename `add_providing` to `start_providing` and `remove_providing` to `stop_providing`. * Add a `bootstrap` function that implements a (almost) standard Kademlia bootstrapping procedure. * Rename `KademliaOut` to `KademliaEvent` with an updated list of constructors (some renaming). All events that report query results now report a `Result` to uniformly permit reporting of errors. The following refactorings are made: * Introduce some constants. * Consolidate `query.rs` and `write.rs` behind a common query interface to reduce duplication and facilitate better code reuse, introducing the notion of a query peer iterator. `query/peers/closest.rs` contains the code that was formerly in `query.rs`. `query/peers/fixed.rs` contains a modified variant of `write.rs` (which is removed). The new `query.rs` provides an interface for working with a collection of queries, taking over some code from `behaviour.rs`. * Reduce code duplication in tests and use the current_thread runtime for polling swarms to avoid spurious errors in the test output due to aborted connections when a test finishes prematurely (e.g. because a quorum of results has been collected). * Some additions / improvements to the existing tests. * Fix test. * Fix rebase. * Tweak kad-ipfs example. * Incorporate some feedback. * Provide easy access and conversion to keys in error results.
This commit is contained in:
@ -20,16 +20,11 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{
|
||||
GetValueResult,
|
||||
Kademlia,
|
||||
KademliaOut,
|
||||
kbucket::{self, Distance},
|
||||
record::{Record, RecordStore},
|
||||
};
|
||||
use futures::{future, prelude::*};
|
||||
use super::*;
|
||||
|
||||
use crate::kbucket::Distance;
|
||||
use futures::future;
|
||||
use libp2p_core::{
|
||||
PeerId,
|
||||
Swarm,
|
||||
Transport,
|
||||
identity,
|
||||
@ -41,10 +36,10 @@ use libp2p_core::{
|
||||
};
|
||||
use libp2p_secio::SecioConfig;
|
||||
use libp2p_yamux as yamux;
|
||||
use rand::random;
|
||||
use rand::{Rng, random, thread_rng};
|
||||
use std::{collections::HashSet, iter::FromIterator, io, num::NonZeroU8, u64};
|
||||
use tokio::runtime::Runtime;
|
||||
use multihash::Hash;
|
||||
use tokio::runtime::current_thread;
|
||||
use multihash::Hash::SHA2256;
|
||||
|
||||
type TestSwarm = Swarm<
|
||||
Boxed<(PeerId, StreamMuxerBox), io::Error>,
|
||||
@ -72,7 +67,8 @@ fn build_nodes(num: usize) -> (u64, Vec<TestSwarm>) {
|
||||
.map_err(|e| panic!("Failed to create transport: {:?}", e))
|
||||
.boxed();
|
||||
|
||||
let kad = Kademlia::new(local_public_key.clone().into_peer_id());
|
||||
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()));
|
||||
}
|
||||
|
||||
@ -85,54 +81,48 @@ fn build_nodes(num: usize) -> (u64, Vec<TestSwarm>) {
|
||||
(port_base, result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_iter() {
|
||||
fn distances(key: &kbucket::Key<PeerId>, peers: Vec<PeerId>) -> Vec<Distance> {
|
||||
peers.into_iter()
|
||||
.map(kbucket::Key::from)
|
||||
.map(|k| k.distance(key))
|
||||
.collect()
|
||||
fn build_connected_nodes(total: usize, step: usize) -> (Vec<PeerId>, Vec<TestSwarm>) {
|
||||
let (port_base, mut swarms) = build_nodes(total);
|
||||
let swarm_ids: Vec<_> = swarms.iter().map(Swarm::local_peer_id).cloned().collect();
|
||||
|
||||
let mut i = 0;
|
||||
for (j, peer) in swarm_ids.iter().enumerate().skip(1) {
|
||||
if i < swarm_ids.len() {
|
||||
swarms[i].add_address(&peer, Protocol::Memory(port_base + j as u64).into());
|
||||
}
|
||||
if j % step == 0 {
|
||||
i += step;
|
||||
}
|
||||
}
|
||||
|
||||
fn run(n: usize) {
|
||||
// Build `n` nodes. Node `n` knows about node `n-1`, node `n-1` knows about node `n-2`, etc.
|
||||
// Node `n` is queried for a random peer and should return nodes `1..n-1` sorted by
|
||||
// their distances to that peer.
|
||||
(swarm_ids, swarms)
|
||||
}
|
||||
|
||||
let (port_base, mut swarms) = build_nodes(n);
|
||||
let swarm_ids: Vec<_> = swarms.iter().map(Swarm::local_peer_id).cloned().collect();
|
||||
#[test]
|
||||
fn bootstrap() {
|
||||
fn run<G: rand::Rng>(rng: &mut G) {
|
||||
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);
|
||||
|
||||
// Connect each swarm in the list to its predecessor in the list.
|
||||
for (i, (swarm, peer)) in &mut swarms.iter_mut().skip(1).zip(swarm_ids.clone()).enumerate() {
|
||||
swarm.add_address(&peer, Protocol::Memory(port_base + i as u64).into())
|
||||
}
|
||||
swarms[0].bootstrap();
|
||||
|
||||
// Ask the last peer in the list to search a random peer. The search should
|
||||
// propagate backwards through the list of peers.
|
||||
let search_target = PeerId::random();
|
||||
let search_target_key = kbucket::Key::from(search_target.clone());
|
||||
swarms.last_mut().unwrap().find_node(search_target.clone());
|
||||
|
||||
// Set up expectations.
|
||||
let expected_swarm_id = swarm_ids.last().unwrap().clone();
|
||||
let expected_peer_ids: Vec<_> = swarm_ids.iter().cloned().take(n - 1).collect();
|
||||
let mut expected_distances = distances(&search_target_key, expected_peer_ids.clone());
|
||||
expected_distances.sort();
|
||||
// Expected known peers
|
||||
let expected_known = swarm_ids.iter().skip(1).cloned().collect::<HashSet<_>>();
|
||||
|
||||
// Run test
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for (i, swarm) in swarms.iter_mut().enumerate() {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::FindNodeResult {
|
||||
key, closer_peers
|
||||
})) => {
|
||||
assert_eq!(key, search_target);
|
||||
assert_eq!(swarm_ids[i], expected_swarm_id);
|
||||
assert!(expected_peer_ids.iter().all(|p| closer_peers.contains(p)));
|
||||
let key = kbucket::Key::from(key);
|
||||
assert_eq!(expected_distances, distances(&key, closer_peers));
|
||||
Async::Ready(Some(KademliaEvent::BootstrapResult(Ok(ok)))) => {
|
||||
assert_eq!(i, 0);
|
||||
assert_eq!(ok.peer, swarm_ids[0]);
|
||||
let known = swarm.kbuckets.iter()
|
||||
.map(|e| e.node.key.preimage().clone())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(expected_known, known);
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
@ -142,10 +132,66 @@ fn query_iter() {
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
for n in 2..=8 { run(n) }
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0 .. 10 {
|
||||
run(&mut rng)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_iter() {
|
||||
fn distances<K>(key: &kbucket::Key<K>, peers: Vec<PeerId>) -> Vec<Distance> {
|
||||
peers.into_iter()
|
||||
.map(kbucket::Key::from)
|
||||
.map(|k| k.distance(key))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn run<G: Rng>(rng: &mut G) {
|
||||
let num_total = rng.gen_range(2, 20);
|
||||
let (swarm_ids, mut swarms) = build_connected_nodes(num_total, 1);
|
||||
|
||||
// Ask the first peer in the list to search a random peer. The search should
|
||||
// propagate forwards through the list of peers.
|
||||
let search_target = PeerId::random();
|
||||
let search_target_key = kbucket::Key::from(search_target.clone());
|
||||
swarms[0].get_closest_peers(search_target.clone());
|
||||
|
||||
// Set up expectations.
|
||||
let expected_swarm_id = swarm_ids[0].clone();
|
||||
let expected_peer_ids: Vec<_> = swarm_ids.iter().skip(1).cloned().collect();
|
||||
let mut expected_distances = distances(&search_target_key, expected_peer_ids.clone());
|
||||
expected_distances.sort();
|
||||
|
||||
// Run test
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for (i, swarm) in swarms.iter_mut().enumerate() {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaEvent::GetClosestPeersResult(Ok(ok)))) => {
|
||||
assert_eq!(ok.key, search_target);
|
||||
assert_eq!(swarm_ids[i], expected_swarm_id);
|
||||
assert!(expected_peer_ids.iter().all(|p| ok.peers.contains(p)));
|
||||
let key = kbucket::Key::new(ok.key);
|
||||
assert_eq!(expected_distances, distances(&key, ok.peers));
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
Async::NotReady => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
}
|
||||
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0 .. 10 {
|
||||
run(&mut rng)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -162,16 +208,16 @@ fn unresponsive_not_returned_direct() {
|
||||
|
||||
// Ask first to search a random value.
|
||||
let search_target = PeerId::random();
|
||||
swarms[0].find_node(search_target.clone());
|
||||
swarms[0].get_closest_peers(search_target.clone());
|
||||
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::FindNodeResult { key, closer_peers })) => {
|
||||
assert_eq!(key, search_target);
|
||||
assert_eq!(closer_peers.len(), 0);
|
||||
Async::Ready(Some(KademliaEvent::GetClosestPeersResult(Ok(ok)))) => {
|
||||
assert_eq!(ok.key, search_target);
|
||||
assert_eq!(ok.peers.len(), 0);
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
@ -182,7 +228,6 @@ fn unresponsive_not_returned_direct() {
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -207,17 +252,17 @@ fn unresponsive_not_returned_indirect() {
|
||||
|
||||
// Ask second to search a random value.
|
||||
let search_target = PeerId::random();
|
||||
swarms[1].find_node(search_target.clone());
|
||||
swarms[1].get_closest_peers(search_target.clone());
|
||||
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::FindNodeResult { key, closer_peers })) => {
|
||||
assert_eq!(key, search_target);
|
||||
assert_eq!(closer_peers.len(), 1);
|
||||
assert_eq!(closer_peers[0], first_peer_id);
|
||||
Async::Ready(Some(KademliaEvent::GetClosestPeersResult(Ok(ok)))) => {
|
||||
assert_eq!(ok.key, search_target);
|
||||
assert_eq!(ok.peers.len(), 1);
|
||||
assert_eq!(ok.peers[0], first_peer_id);
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
@ -228,38 +273,34 @@ fn unresponsive_not_returned_indirect() {
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn get_value_not_found() {
|
||||
fn get_record_not_found() {
|
||||
let (port_base, mut swarms) = build_nodes(3);
|
||||
|
||||
let swarm_ids: Vec<_> = swarms.iter()
|
||||
.map(|swarm| Swarm::local_peer_id(&swarm).clone()).collect();
|
||||
let swarm_ids: Vec<_> = swarms.iter().map(Swarm::local_peer_id).cloned().collect();
|
||||
|
||||
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 target_key = multihash::encode(Hash::SHA2256, &vec![1,2,3]).unwrap();
|
||||
let num_results = NonZeroU8::new(1).unwrap();
|
||||
swarms[0].get_value(&target_key, num_results);
|
||||
let target_key = multihash::encode(SHA2256, &vec![1,2,3]).unwrap();
|
||||
swarms[0].get_record(&target_key, Quorum::One);
|
||||
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::GetValueResult(result))) => {
|
||||
if let GetValueResult::NotFound { key, closest_peers } = result {
|
||||
Async::Ready(Some(KademliaEvent::GetRecordResult(Err(e)))) => {
|
||||
if let GetRecordError::NotFound { key, closest_peers, } = e {
|
||||
assert_eq!(key, target_key);
|
||||
assert_eq!(closest_peers.len(), 2);
|
||||
assert!(closest_peers.contains(&swarm_ids[1]));
|
||||
assert!(closest_peers.contains(&swarm_ids[2]));
|
||||
return Ok(Async::Ready(()));
|
||||
} else {
|
||||
panic!("Expected GetValueResult::NotFound event");
|
||||
panic!("Unexpected error result: {:?}", e);
|
||||
}
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
@ -270,62 +311,37 @@ fn get_value_not_found() {
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_value() {
|
||||
fn run() {
|
||||
// Build a test that checks if PUT_VALUE gets correctly propagated in
|
||||
// a nontrivial topology:
|
||||
// [31]
|
||||
// / \
|
||||
// [29] [30]
|
||||
// /|\ /|\
|
||||
// [0]..[14] [15]..[28]
|
||||
//
|
||||
// Nodes [29] and [30] have less than kbuckets::MAX_NODES_PER_BUCKET
|
||||
// peers to avoid the situation when the bucket may be overflowed and
|
||||
// some of the connections are dropped from the routing table
|
||||
let (port_base, mut swarms) = build_nodes(32);
|
||||
fn run<G: rand::Rng>(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 swarm_ids: Vec<_> = swarms.iter()
|
||||
.map(|swarm| Swarm::local_peer_id(&swarm).clone()).collect();
|
||||
|
||||
// Connect swarm[30] to each swarm in swarms[..15]
|
||||
for (i, peer) in swarm_ids.iter().take(15).enumerate() {
|
||||
swarms[30].add_address(&peer, Protocol::Memory(port_base + i as u64).into());
|
||||
}
|
||||
|
||||
// Connect swarm[29] to each swarm in swarms[15..29]
|
||||
for (i, peer) in swarm_ids.iter().skip(15).take(14).enumerate() {
|
||||
swarms[29].add_address(&peer, Protocol::Memory(port_base + (i + 15) as u64).into());
|
||||
}
|
||||
|
||||
// Connect swarms[31] to swarms[29, 30]
|
||||
swarms[31].add_address(&swarm_ids[30], Protocol::Memory(port_base + 30 as u64).into());
|
||||
swarms[31].add_address(&swarm_ids[29], Protocol::Memory(port_base + 29 as u64).into());
|
||||
|
||||
let target_key = multihash::encode(Hash::SHA2256, &vec![1,2,3]).unwrap();
|
||||
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(&kbucket::Key::from(target_key.clone()))))
|
||||
.map(|id| (id.clone(), kbucket::Key::from(id.clone()).distance(&bucket_key)))
|
||||
.collect();
|
||||
|
||||
sorted_peer_ids.sort_by(|(_, d1), (_, d2)| d1.cmp(d2));
|
||||
|
||||
let closest: HashSet<PeerId> = HashSet::from_iter(sorted_peer_ids.into_iter().map(|(id, _)| id));
|
||||
let closest = HashSet::from_iter(sorted_peer_ids.into_iter().map(|(id, _)| id));
|
||||
|
||||
swarms[31].put_value(target_key.clone(), vec![4,5,6]);
|
||||
let record = Record { key: key.clone(), value: vec![4,5,6] };
|
||||
swarms[0].put_record(record, Quorum::All);
|
||||
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
let mut check_results = false;
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::PutValueResult{ .. })) => {
|
||||
Async::Ready(Some(KademliaEvent::PutRecordResult(Ok(_)))) => {
|
||||
check_results = true;
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
@ -337,25 +353,27 @@ fn put_value() {
|
||||
if check_results {
|
||||
let mut have: HashSet<_> = Default::default();
|
||||
|
||||
for (i, swarm) in swarms.iter().take(31).enumerate() {
|
||||
if swarm.records.get(&target_key).is_some() {
|
||||
for (i, swarm) in swarms.iter().skip(1).enumerate() {
|
||||
if swarm.records.get(&key).is_some() {
|
||||
have.insert(swarm_ids[i].clone());
|
||||
}
|
||||
}
|
||||
|
||||
let intersection: HashSet<_> = have.intersection(&closest).collect();
|
||||
|
||||
assert_eq!(have.len(), kbucket::MAX_NODES_PER_BUCKET);
|
||||
assert_eq!(intersection.len(), kbucket::MAX_NODES_PER_BUCKET);
|
||||
assert_eq!(have.len(), kbucket::K_VALUE);
|
||||
assert_eq!(intersection.len(), kbucket::K_VALUE);
|
||||
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0 .. 10 {
|
||||
run();
|
||||
run(&mut rng);
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,36 +381,28 @@ fn put_value() {
|
||||
fn get_value() {
|
||||
let (port_base, mut swarms) = build_nodes(3);
|
||||
|
||||
let swarm_ids: Vec<_> = swarms.iter()
|
||||
.map(|swarm| Swarm::local_peer_id(&swarm).clone()).collect();
|
||||
let swarm_ids: Vec<_> = swarms.iter().map(Swarm::local_peer_id).cloned().collect();
|
||||
|
||||
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 target_key = multihash::encode(Hash::SHA2256, &vec![1,2,3]).unwrap();
|
||||
let target_value = vec![4,5,6];
|
||||
|
||||
let num_results = NonZeroU8::new(1).unwrap();
|
||||
swarms[1].records.put(Record {
|
||||
key: target_key.clone(),
|
||||
value: target_value.clone()
|
||||
}).unwrap();
|
||||
swarms[0].get_value(&target_key, num_results);
|
||||
let record = Record {
|
||||
key: multihash::encode(SHA2256, &vec![1,2,3]).unwrap(),
|
||||
value: vec![4,5,6]
|
||||
};
|
||||
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
swarms[1].records.put(record.clone()).unwrap();
|
||||
swarms[0].get_record(&record.key, Quorum::One);
|
||||
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::GetValueResult(result))) => {
|
||||
if let GetValueResult::Found { results } = result {
|
||||
assert_eq!(results.len(), 1);
|
||||
let record = results.first().unwrap();
|
||||
assert_eq!(record.key, target_key);
|
||||
assert_eq!(record.value, target_value);
|
||||
return Ok(Async::Ready(()));
|
||||
} else {
|
||||
panic!("Expected GetValueResult::Found event");
|
||||
}
|
||||
Async::Ready(Some(KademliaEvent::GetRecordResult(Ok(ok)))) => {
|
||||
assert_eq!(ok.records.len(), 1);
|
||||
assert_eq!(ok.records.first(), Some(&record));
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
Async::NotReady => break,
|
||||
@ -402,56 +412,43 @@ fn get_value() {
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_value_multiple() {
|
||||
// Check that if we have responses from multiple peers, a correct number of
|
||||
// results is returned.
|
||||
let num_results = NonZeroU8::new(10).unwrap();
|
||||
let (port_base, mut swarms) = build_nodes(2 + num_results.get() as usize);
|
||||
let num_nodes = 12;
|
||||
let (_swarm_ids, mut swarms) = build_connected_nodes(num_nodes, num_nodes);
|
||||
let num_results = 10;
|
||||
|
||||
let swarm_ids: Vec<_> = swarms.iter()
|
||||
.map(|swarm| Swarm::local_peer_id(&swarm).clone()).collect();
|
||||
let record = Record {
|
||||
key: multihash::encode(SHA2256, &vec![1,2,3]).unwrap(),
|
||||
value: vec![4,5,6],
|
||||
};
|
||||
|
||||
let target_key = multihash::encode(Hash::SHA2256, &vec![1,2,3]).unwrap();
|
||||
let target_value = vec![4,5,6];
|
||||
|
||||
for (i, swarm_id) in swarm_ids.iter().skip(1).enumerate() {
|
||||
swarms[i + 1].records.put(Record {
|
||||
key: target_key.clone(),
|
||||
value: target_value.clone()
|
||||
}).unwrap();
|
||||
swarms[0].add_address(&swarm_id, Protocol::Memory(port_base + (i + 1) as u64).into());
|
||||
for i in 0 .. num_nodes {
|
||||
swarms[i].records.put(record.clone()).unwrap();
|
||||
}
|
||||
|
||||
swarms[0].records.put(Record { key: target_key.clone(), value: target_value.clone() }).unwrap();
|
||||
swarms[0].get_value(&target_key, num_results);
|
||||
let quorum = Quorum::N(NonZeroU8::new(num_results as u8).unwrap());
|
||||
swarms[0].get_record(&record.key, quorum);
|
||||
|
||||
Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
current_thread::run(
|
||||
future::poll_fn(move || {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll().unwrap() {
|
||||
Async::Ready(Some(KademliaOut::GetValueResult(result))) => {
|
||||
if let GetValueResult::Found { results } = result {
|
||||
assert_eq!(results.len(), num_results.get() as usize);
|
||||
let record = results.first().unwrap();
|
||||
assert_eq!(record.key, target_key);
|
||||
assert_eq!(record.value, target_value);
|
||||
return Ok(Async::Ready(()));
|
||||
} else {
|
||||
panic!("Expected GetValueResult::Found event");
|
||||
}
|
||||
Async::Ready(Some(KademliaEvent::GetRecordResult(Ok(ok)))) => {
|
||||
assert_eq!(ok.records.len(), num_results);
|
||||
assert_eq!(ok.records.first(), Some(&record));
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Async::Ready(_) => (),
|
||||
Async::NotReady => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
Reference in New Issue
Block a user