Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Commit 7b5e5aa

Browse files
committed
network-devp2p: More efficient nearest node search.
The discovery algorithm to identify the nearest k nodes does not need to scan all entries in all buckets.
1 parent 9ecd7d0 commit 7b5e5aa

File tree

1 file changed

+42
-25
lines changed

1 file changed

+42
-25
lines changed

util/network-devp2p/src/discovery.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
use ethcore_bytes::Bytes;
1818
use std::net::SocketAddr;
19-
use std::collections::{HashSet, HashMap, BTreeMap, VecDeque};
19+
use std::collections::{HashSet, HashMap, VecDeque};
2020
use std::mem;
2121
use std::default::Default;
2222
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
@@ -303,35 +303,52 @@ impl Discovery {
303303
}
304304

305305
fn nearest_node_entries(&self, target: &NodeId) -> Vec<NodeEntry> {
306-
let mut found: BTreeMap<H256, Vec<&NodeEntry>> = BTreeMap::new();
307-
let mut count = 0;
308306
let target_hash = keccak(target);
307+
let target_distance = self.id_hash ^ target_hash;
308+
309+
let mut ret = Vec::<NodeEntry>::with_capacity(BUCKET_SIZE);
310+
311+
// Sort bucket entries by distance to target and append to end of result vector.
312+
let append_bucket = |results: &mut Vec<NodeEntry>, bucket: &NodeBucket| -> bool {
313+
let mut sorted_entries: Vec<&BucketEntry> = bucket.nodes.iter().collect();
314+
sorted_entries.sort_unstable_by_key(|entry| entry.id_hash ^ target_hash);
315+
316+
let remaining_capacity = results.capacity() - results.len();
317+
let to_append = if remaining_capacity < sorted_entries.len() {
318+
&sorted_entries[0..remaining_capacity]
319+
} else {
320+
&sorted_entries
321+
};
322+
for entry in to_append.iter() {
323+
results.push(entry.address.clone());
324+
}
325+
results.len() == results.capacity()
326+
};
309327

310-
// Sort nodes by distance to target.
311-
for bucket in &self.node_buckets {
312-
for node in &bucket.nodes {
313-
let dist = target_hash ^ node.id_hash;
314-
found.entry(dist).or_insert_with(Vec::new).push(&node.address);
315-
if count == BUCKET_SIZE {
316-
// delete the most distant element
317-
let remove = {
318-
let (key, last) = found.iter_mut().next_back().expect("Last element is always Some when count > 0");
319-
last.pop();
320-
if last.is_empty() { Some(key.clone()) } else { None }
321-
};
322-
if let Some(remove) = remove {
323-
found.remove(&remove);
324-
}
325-
}
326-
else {
327-
count += 1;
328+
// This algorithm leverages the structure of the routing table to efficiently find the
329+
// nearest entries to a target hash. First, we compute the XOR distance from this node to
330+
// the target. One a first pass, we iterate from the MSB of the distance, stopping at any
331+
// buckets where the distance bit is set, and skipping the buckets where it is unset. These
332+
// must be in order the nearest to the target. On a second pass, we traverse from LSB to
333+
// MSB, appending the buckets skipped on the first pass. The reason this works is that all
334+
// entries in bucket i have a common prefix of length exactly 32 - i - 1 with the ID of this
335+
// node.
336+
337+
for i in 0..ADDRESS_BITS {
338+
if ((target_distance[i / 8] << (i % 8)) & 0x80) != 0 {
339+
let bucket = &self.node_buckets[ADDRESS_BITS - i - 1];
340+
if !bucket.nodes.is_empty() && append_bucket(&mut ret, bucket) {
341+
return ret;
328342
}
329343
}
330344
}
331-
332-
let mut ret:Vec<NodeEntry> = Vec::new();
333-
for nodes in found.values() {
334-
ret.extend(nodes.iter().map(|&n| n.clone()));
345+
for i in (0..ADDRESS_BITS).rev() {
346+
if ((target_distance[i / 8] << (i % 8)) & 0x80) == 0 {
347+
let bucket = &self.node_buckets[ADDRESS_BITS - i - 1];
348+
if !bucket.nodes.is_empty() && append_bucket(&mut ret, bucket) {
349+
return ret;
350+
}
351+
}
335352
}
336353
ret
337354
}

0 commit comments

Comments
 (0)