Fix lookup to sort and query closest nodes

This commit is contained in:
kdeme 2020-12-15 13:24:57 +01:00
parent 0820dbba46
commit 68c9b7b3ad
No known key found for this signature in database
GPG Key ID: 4E8DD21420AF43F5
3 changed files with 28 additions and 15 deletions

View File

@ -73,7 +73,7 @@
## This might be a concern for mobile devices. ## This might be a concern for mobile devices.
import import
std/[tables, sets, options, math, sequtils], std/[tables, sets, options, math, sequtils, algorithm],
stew/shims/net as stewNet, json_serialization/std/net, stew/shims/net as stewNet, json_serialization/std/net,
stew/endians2, chronicles, chronos, stint, bearssl, stew/endians2, chronicles, chronos, stint, bearssl,
eth/[rlp, keys, async_utils], eth/[rlp, keys, async_utils],
@ -633,6 +633,7 @@ proc lookupWorker(d: Protocol, destNode: Node, target: NodeId):
let dists = lookupDistances(target, destNode.id) let dists = lookupDistances(target, destNode.id)
var i = 0 var i = 0
# TODO: We can make use of the multiple distances here now. # TODO: We can make use of the multiple distances here now.
# Do findNode requests with different distances until we hit limits.
while i < lookupRequestLimit and result.len < findNodeResultLimit: while i < lookupRequestLimit and result.len < findNodeResultLimit:
let r = await d.findNode(destNode, @[dists[i]]) let r = await d.findNode(destNode, @[dists[i]])
# TODO: Handle failures better. E.g. stop on different failures than timeout # TODO: Handle failures better. E.g. stop on different failures than timeout
@ -648,21 +649,25 @@ proc lookup*(d: Protocol, target: NodeId): Future[seq[Node]]
{.async, raises: [Exception, Defect].} = {.async, raises: [Exception, Defect].} =
## Perform a lookup for the given target, return the closest n nodes to the ## Perform a lookup for the given target, return the closest n nodes to the
## target. Maximum value for n is `BUCKET_SIZE`. ## target. Maximum value for n is `BUCKET_SIZE`.
# TODO: Sort the returned nodes on distance # `closestNodes` holds the k closest nodes to target found, sorted by distance
# Also use unseen nodes as a form of validation. # Unvalidated nodes are used for requests as a form of validation.
result = d.routingTable.neighbours(target, BUCKET_SIZE, seenOnly = false) var closestNodes = d.routingTable.neighbours(target, BUCKET_SIZE,
var asked = initHashSet[NodeId]() seenOnly = false)
asked.incl(d.localNode.id)
var seen = asked var asked, seen = initHashSet[NodeId]()
for node in result: asked.incl(d.localNode.id) # No need to ask our own node
seen.incl(d.localNode.id) # No need to discover our own node
for node in closestNodes:
seen.incl(node.id) seen.incl(node.id)
var pendingQueries = newSeqOfCap[Future[seq[Node]]](alpha) var pendingQueries = newSeqOfCap[Future[seq[Node]]](alpha)
while true: while true:
var i = 0 var i = 0
while i < result.len and pendingQueries.len < alpha: # Doing `alpha` amount of requests at once as long as closer non queried
let n = result[i] # nodes are discovered.
while i < closestNodes.len and pendingQueries.len < alpha:
let n = closestNodes[i]
if not asked.containsOrIncl(n.id): if not asked.containsOrIncl(n.id):
pendingQueries.add(d.lookupWorker(n, target)) pendingQueries.add(d.lookupWorker(n, target))
inc i inc i
@ -682,10 +687,19 @@ proc lookup*(d: Protocol, target: NodeId): Future[seq[Node]]
error "Resulting query should have beeen in the pending queries" error "Resulting query should have beeen in the pending queries"
let nodes = query.read let nodes = query.read
# TODO: Remove node on timed-out query?
for n in nodes: for n in nodes:
if not seen.containsOrIncl(n.id): if not seen.containsOrIncl(n.id):
if result.len < BUCKET_SIZE: # If it wasn't seen before, insert node while remaining sorted
result.add(n) closestNodes.insert(n, closestNodes.lowerBound(n,
proc(x: Node, n: Node): int =
cmp(distanceTo(x, target), distanceTo(n, target))
))
if closestNodes.len > BUCKET_SIZE:
closestNodes.del(closestNodes.high())
return closestNodes
proc lookupRandom*(d: Protocol): Future[seq[Node]] proc lookupRandom*(d: Protocol): Future[seq[Node]]
{.async, raises:[Exception, Defect].} = {.async, raises:[Exception, Defect].} =

View File

@ -87,7 +87,7 @@ const
DefaultTableIpLimits* = TableIpLimits(tableIpLimit: DefaultTableIpLimit, DefaultTableIpLimits* = TableIpLimits(tableIpLimit: DefaultTableIpLimit,
bucketIpLimit: DefaultBucketIpLimit) bucketIpLimit: DefaultBucketIpLimit)
proc distanceTo(n: Node, id: NodeId): UInt256 = proc distanceTo*(n: Node, id: NodeId): UInt256 =
## Calculate the distance to a NodeId. ## Calculate the distance to a NodeId.
n.id xor id n.id xor id

View File

@ -256,8 +256,7 @@ procSuite "Discovery v5 Tests":
let target = nodes[i] let target = nodes[i]
let discovered = await nodes[nodeCount-1].lookup(target.localNode.id) let discovered = await nodes[nodeCount-1].lookup(target.localNode.id)
debug "Lookup result", target = target.localNode, discovered debug "Lookup result", target = target.localNode, discovered
# if lookUp would return ordered on distance we could check discovered[0] check discovered[0] == target.localNode
check discovered.contains(target.localNode)
for node in nodes: for node in nodes:
await node.closeWait() await node.closeWait()