mirror of https://github.com/status-im/nim-eth.git
Support findnode with multiple distances in discv5.1
This commit is contained in:
parent
8042d72711
commit
ce2cd2323c
|
@ -4,12 +4,14 @@ import
|
||||||
stew/byteutils, confutils/std/net,
|
stew/byteutils, confutils/std/net,
|
||||||
eth/keys, eth/net/nat, enr, node
|
eth/keys, eth/net/nat, enr, node
|
||||||
|
|
||||||
|
### This is all just temporary to be compatible with both versions
|
||||||
const UseDiscv51* {.booldefine.} = false
|
const UseDiscv51* {.booldefine.} = false
|
||||||
|
|
||||||
when UseDiscv51:
|
when UseDiscv51:
|
||||||
import protocolv1
|
import protocolv1
|
||||||
else:
|
else:
|
||||||
import protocol
|
import protocol
|
||||||
|
###
|
||||||
|
|
||||||
type
|
type
|
||||||
DiscoveryCmd* = enum
|
DiscoveryCmd* = enum
|
||||||
|
@ -173,7 +175,10 @@ proc run(config: DiscoveryConf) =
|
||||||
else:
|
else:
|
||||||
echo "No Pong message returned"
|
echo "No Pong message returned"
|
||||||
of findnode:
|
of findnode:
|
||||||
let nodes = waitFor d.findNode(config.findNodeTarget, config.distance)
|
when UseDiscv51:
|
||||||
|
let nodes = waitFor d.findNode(config.findNodeTarget, @[config.distance])
|
||||||
|
else:
|
||||||
|
let nodes = waitFor d.findNode(config.findNodeTarget, config.distance)
|
||||||
if nodes.isOk():
|
if nodes.isOk():
|
||||||
echo "Received valid records:"
|
echo "Received valid records:"
|
||||||
for node in nodes[]:
|
for node in nodes[]:
|
||||||
|
|
|
@ -267,12 +267,22 @@ proc handlePing(d: Protocol, fromId: NodeId, fromAddr: Address,
|
||||||
|
|
||||||
proc handleFindNode(d: Protocol, fromId: NodeId, fromAddr: Address,
|
proc handleFindNode(d: Protocol, fromId: NodeId, fromAddr: Address,
|
||||||
fn: FindNodeMessage, reqId: RequestId) =
|
fn: FindNodeMessage, reqId: RequestId) =
|
||||||
if fn.distance == 0:
|
if fn.distances.len == 0:
|
||||||
|
d.sendNodes(fromId, fromAddr, reqId, [])
|
||||||
|
elif fn.distances.contains(0):
|
||||||
|
# A request for our own record.
|
||||||
|
# It would be a weird request if there are more distances next to 0
|
||||||
|
# requested, so in this case lets just pass only our own. TODO: OK?
|
||||||
d.sendNodes(fromId, fromAddr, reqId, [d.localNode])
|
d.sendNodes(fromId, fromAddr, reqId, [d.localNode])
|
||||||
else:
|
else:
|
||||||
let distance = min(fn.distance, 256)
|
# TODO: Still deduplicate also?
|
||||||
d.sendNodes(fromId, fromAddr, reqId,
|
if fn.distances.all(proc (x: uint32): bool = return x <= 256):
|
||||||
d.routingTable.neighboursAtDistance(distance, seenOnly = true))
|
d.sendNodes(fromId, fromAddr, reqId,
|
||||||
|
d.routingTable.neighboursAtDistances(fn.distances, seenOnly = true))
|
||||||
|
else:
|
||||||
|
# At least one invalid distance, but the polite node we are, still respond
|
||||||
|
# with empty nodes.
|
||||||
|
d.sendNodes(fromId, fromAddr, reqId, [])
|
||||||
|
|
||||||
proc handleMessage(d: Protocol, srcId: NodeId, fromAddr: Address,
|
proc handleMessage(d: Protocol, srcId: NodeId, fromAddr: Address,
|
||||||
message: Message) {.raises:[Exception].} =
|
message: Message) {.raises:[Exception].} =
|
||||||
|
@ -473,7 +483,7 @@ proc waitMessage(d: Protocol, fromNode: Node, reqId: RequestId):
|
||||||
d.awaitedMessages[key] = result
|
d.awaitedMessages[key] = result
|
||||||
|
|
||||||
proc verifyNodesRecords*(enrs: openarray[Record], fromNode: Node,
|
proc verifyNodesRecords*(enrs: openarray[Record], fromNode: Node,
|
||||||
distance: uint32): seq[Node] {.raises: [Defect].} =
|
distances: varargs[uint32]): seq[Node] {.raises: [Defect].} =
|
||||||
## Verify and convert ENRs to a sequence of nodes. Only ENRs that pass
|
## Verify and convert ENRs to a sequence of nodes. Only ENRs that pass
|
||||||
## verification will be added. ENRs are verified for duplicates, invalid
|
## verification will be added. ENRs are verified for duplicates, invalid
|
||||||
## addresses and invalid distances.
|
## addresses and invalid distances.
|
||||||
|
@ -500,11 +510,12 @@ proc verifyNodesRecords*(enrs: openarray[Record], fromNode: Node,
|
||||||
trace "Nodes reply contained record with invalid ip-address",
|
trace "Nodes reply contained record with invalid ip-address",
|
||||||
record = n.record.toURI, sender = fromNode.record.toURI, node = $n
|
record = n.record.toURI, sender = fromNode.record.toURI, node = $n
|
||||||
continue
|
continue
|
||||||
# Check if returned node has the requested distance.
|
# Check if returned node has one of the requested distances.
|
||||||
if logDist(n.id, fromNode.id) != min(distance, 256):
|
if not distances.contains(logDist(n.id, fromNode.id)):
|
||||||
warn "Nodes reply contained record with incorrect distance",
|
warn "Nodes reply contained record with incorrect distance",
|
||||||
record = n.record.toURI, sender = fromNode.record.toURI
|
record = n.record.toURI, sender = fromNode.record.toURI
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# No check on UDP port and thus any port is allowed, also the so called
|
# No check on UDP port and thus any port is allowed, also the so called
|
||||||
# "well-known" ports.
|
# "well-known" ports.
|
||||||
|
|
||||||
|
@ -564,17 +575,17 @@ proc ping*(d: Protocol, toNode: Node):
|
||||||
d.replaceNode(toNode)
|
d.replaceNode(toNode)
|
||||||
return err("Pong message not received in time")
|
return err("Pong message not received in time")
|
||||||
|
|
||||||
proc findNode*(d: Protocol, toNode: Node, distance: uint32):
|
proc findNode*(d: Protocol, toNode: Node, distances: seq[uint32]):
|
||||||
Future[DiscResult[seq[Node]]] {.async, raises: [Exception, Defect].} =
|
Future[DiscResult[seq[Node]]] {.async, raises: [Exception, Defect].} =
|
||||||
## Send a discovery findNode message.
|
## Send a discovery findNode message.
|
||||||
##
|
##
|
||||||
## Returns the received nodes or an error.
|
## Returns the received nodes or an error.
|
||||||
## Received ENRs are already validated and converted to `Node`.
|
## Received ENRs are already validated and converted to `Node`.
|
||||||
let reqId = d.sendMessage(toNode, FindNodeMessage(distance: distance))
|
let reqId = d.sendMessage(toNode, FindNodeMessage(distances: distances))
|
||||||
let nodes = await d.waitNodes(toNode, reqId)
|
let nodes = await d.waitNodes(toNode, reqId)
|
||||||
|
|
||||||
if nodes.isOk:
|
if nodes.isOk:
|
||||||
let res = verifyNodesRecords(nodes.get(), toNode, distance)
|
let res = verifyNodesRecords(nodes.get(), toNode, distances)
|
||||||
d.routingTable.setJustSeen(toNode)
|
d.routingTable.setJustSeen(toNode)
|
||||||
return ok(res)
|
return ok(res)
|
||||||
else:
|
else:
|
||||||
|
@ -596,8 +607,9 @@ proc lookupWorker(d: Protocol, destNode: Node, target: NodeId):
|
||||||
Future[seq[Node]] {.async, raises: [Exception, Defect].} =
|
Future[seq[Node]] {.async, raises: [Exception, Defect].} =
|
||||||
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.
|
||||||
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
|
||||||
if r.isOk:
|
if r.isOk:
|
||||||
# TODO: I guess it makes sense to limit here also to `findNodeResultLimit`?
|
# TODO: I guess it makes sense to limit here also to `findNodeResultLimit`?
|
||||||
|
@ -667,7 +679,7 @@ proc resolve*(d: Protocol, id: NodeId): Future[Option[Node]]
|
||||||
|
|
||||||
let node = d.getNode(id)
|
let node = d.getNode(id)
|
||||||
if node.isSome():
|
if node.isSome():
|
||||||
let request = await d.findNode(node.get(), 0)
|
let request = await d.findNode(node.get(), @[0'u32])
|
||||||
|
|
||||||
# TODO: Handle failures better. E.g. stop on different failures than timeout
|
# TODO: Handle failures better. E.g. stop on different failures than timeout
|
||||||
if request.isOk() and request[].len > 0:
|
if request.isOk() and request[].len > 0:
|
||||||
|
@ -690,7 +702,7 @@ proc revalidateNode*(d: Protocol, n: Node)
|
||||||
if pong.isOK():
|
if pong.isOK():
|
||||||
if pong.get().enrSeq > n.record.seqNum:
|
if pong.get().enrSeq > n.record.seqNum:
|
||||||
# Request new ENR
|
# Request new ENR
|
||||||
let nodes = await d.findNode(n, 0)
|
let nodes = await d.findNode(n, @[0'u32])
|
||||||
if nodes.isOk() and nodes[].len > 0:
|
if nodes.isOk() and nodes[].len > 0:
|
||||||
discard d.addNode(nodes[][0])
|
discard d.addNode(nodes[][0])
|
||||||
|
|
||||||
|
|
|
@ -321,6 +321,20 @@ proc neighboursAtDistance*(r: RoutingTable, distance: uint32,
|
||||||
# that are exactly the requested distance.
|
# that are exactly the requested distance.
|
||||||
keepIf(result, proc(n: Node): bool = logDist(n.id, r.thisNode.id) == distance)
|
keepIf(result, proc(n: Node): bool = logDist(n.id, r.thisNode.id) == distance)
|
||||||
|
|
||||||
|
proc neighboursAtDistances*(r: RoutingTable, distances: seq[uint32],
|
||||||
|
k: int = BUCKET_SIZE, seenOnly = false): seq[Node] =
|
||||||
|
## Return up to k neighbours at given logarithmic distances.
|
||||||
|
# TODO: This will currently return nodes with neighbouring distances on the
|
||||||
|
# first one prioritize. It might end up not including all the node distances
|
||||||
|
# requested. Need to rework the logic here and not use the neighbours call.
|
||||||
|
if distances.len > 0:
|
||||||
|
result = r.neighbours(idAtDistance(r.thisNode.id, distances[0]), k,
|
||||||
|
seenOnly)
|
||||||
|
# This is a bit silly, first getting closest nodes then to only keep the ones
|
||||||
|
# that are exactly the requested distances.
|
||||||
|
keepIf(result, proc(n: Node): bool =
|
||||||
|
distances.contains(logDist(n.id, r.thisNode.id)))
|
||||||
|
|
||||||
proc len*(r: RoutingTable): int =
|
proc len*(r: RoutingTable): int =
|
||||||
for b in r.buckets: result += b.len
|
for b in r.buckets: result += b.len
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ type
|
||||||
port*: uint16
|
port*: uint16
|
||||||
|
|
||||||
FindNodeMessage* = object
|
FindNodeMessage* = object
|
||||||
distance*: uint32
|
distances*: seq[uint32]
|
||||||
|
|
||||||
NodesMessage* = object
|
NodesMessage* = object
|
||||||
total*: uint32
|
total*: uint32
|
||||||
|
|
|
@ -5,6 +5,8 @@ import
|
||||||
eth/p2p/discoveryv5/[enr, node, routing_table],
|
eth/p2p/discoveryv5/[enr, node, routing_table],
|
||||||
./discv5_test_helper
|
./discv5_test_helper
|
||||||
|
|
||||||
|
|
||||||
|
### This is all just temporary to be compatible with both versions
|
||||||
const UseDiscv51* {.booldefine.} = false
|
const UseDiscv51* {.booldefine.} = false
|
||||||
|
|
||||||
when UseDiscv51:
|
when UseDiscv51:
|
||||||
|
@ -16,6 +18,12 @@ else:
|
||||||
eth/p2p/discoveryv5/[types, encoding],
|
eth/p2p/discoveryv5/[types, encoding],
|
||||||
eth/p2p/discoveryv5/protocol as discv5_protocol
|
eth/p2p/discoveryv5/protocol as discv5_protocol
|
||||||
|
|
||||||
|
proc findNode*(d: discv5_protocol.Protocol, toNode: Node, distances: seq[uint32]):
|
||||||
|
Future[DiscResult[seq[Node]]] =
|
||||||
|
if distances.len > 0:
|
||||||
|
return d.findNode(toNode, distances[0])
|
||||||
|
###
|
||||||
|
|
||||||
procSuite "Discovery v5 Tests":
|
procSuite "Discovery v5 Tests":
|
||||||
let rng = newRng()
|
let rng = newRng()
|
||||||
|
|
||||||
|
@ -189,7 +197,7 @@ procSuite "Discovery v5 Tests":
|
||||||
check idAtDistance(targetId, d) == parse(id, UInt256, 16)
|
check idAtDistance(targetId, d) == parse(id, UInt256, 16)
|
||||||
|
|
||||||
asyncTest "FindNode Test":
|
asyncTest "FindNode Test":
|
||||||
const dist = 253
|
const dist = 253'u32
|
||||||
let
|
let
|
||||||
mainNodeKey = PrivateKey.fromHex(
|
mainNodeKey = PrivateKey.fromHex(
|
||||||
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
|
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
|
||||||
|
@ -209,14 +217,14 @@ procSuite "Discovery v5 Tests":
|
||||||
|
|
||||||
# Get ENR of the node itself
|
# Get ENR of the node itself
|
||||||
var discovered =
|
var discovered =
|
||||||
await discv5_protocol.findNode(testNode, mainNode.localNode, 0)
|
await findNode(testNode, mainNode.localNode, @[0'u32])
|
||||||
check:
|
check:
|
||||||
discovered.isOk
|
discovered.isOk
|
||||||
discovered[].len == 1
|
discovered[].len == 1
|
||||||
discovered[][0] == mainNode.localNode
|
discovered[][0] == mainNode.localNode
|
||||||
# Get ENRs of nodes added at provided logarithmic distance
|
# Get ENRs of nodes added at provided logarithmic distance
|
||||||
discovered =
|
discovered =
|
||||||
await discv5_protocol.findNode(testNode, mainNode.localNode, dist)
|
await findNode(testNode, mainNode.localNode, @[dist])
|
||||||
check discovered.isOk
|
check discovered.isOk
|
||||||
check discovered[].len == 10
|
check discovered[].len == 10
|
||||||
for n in nodes:
|
for n in nodes:
|
||||||
|
@ -224,14 +232,14 @@ procSuite "Discovery v5 Tests":
|
||||||
|
|
||||||
# Too high logarithmic distance, should return no nodes.
|
# Too high logarithmic distance, should return no nodes.
|
||||||
discovered =
|
discovered =
|
||||||
await discv5_protocol.findNode(testNode, mainNode.localNode, 4294967295'u32)
|
await findNode(testNode, mainNode.localNode, @[4294967295'u32])
|
||||||
check:
|
check:
|
||||||
discovered.isOk
|
discovered.isOk
|
||||||
discovered[].len == 0
|
discovered[].len == 0
|
||||||
|
|
||||||
# Logarithmic distance of 256 should only return the testNode
|
# Logarithmic distance of 256 should only return the testNode
|
||||||
discovered =
|
discovered =
|
||||||
await discv5_protocol.findNode(testNode, mainNode.localNode, 256)
|
await findNode(testNode, mainNode.localNode, @[256'u32])
|
||||||
check:
|
check:
|
||||||
discovered.isOk
|
discovered.isOk
|
||||||
discovered[].len == 1
|
discovered[].len == 1
|
||||||
|
@ -239,7 +247,7 @@ procSuite "Discovery v5 Tests":
|
||||||
|
|
||||||
# Empty bucket
|
# Empty bucket
|
||||||
discovered =
|
discovered =
|
||||||
await discv5_protocol.findNode(testNode, mainNode.localNode, 254)
|
await findNode(testNode, mainNode.localNode, @[254'u32])
|
||||||
check discovered.isOk
|
check discovered.isOk
|
||||||
check discovered[].len == 0
|
check discovered[].len == 0
|
||||||
|
|
||||||
|
@ -249,7 +257,7 @@ procSuite "Discovery v5 Tests":
|
||||||
|
|
||||||
# Full bucket
|
# Full bucket
|
||||||
discovered =
|
discovered =
|
||||||
await discv5_protocol.findNode(testNode, mainNode.localNode, dist)
|
await findNode(testNode, mainNode.localNode, @[dist])
|
||||||
check discovered.isOk
|
check discovered.isOk
|
||||||
check discovered[].len == 16
|
check discovered[].len == 16
|
||||||
|
|
||||||
|
@ -276,8 +284,8 @@ procSuite "Discovery v5 Tests":
|
||||||
testNode = initDiscoveryNode(
|
testNode = initDiscoveryNode(
|
||||||
rng, PrivateKey.random(rng[]), localAddress(20302),
|
rng, PrivateKey.random(rng[]), localAddress(20302),
|
||||||
@[mainNode.localNode.record])
|
@[mainNode.localNode.record])
|
||||||
discovered = await discv5_protocol.findNode(testNode, mainNode.localNode,
|
discovered = await findNode(testNode, mainNode.localNode,
|
||||||
closestDistance)
|
@[closestDistance])
|
||||||
|
|
||||||
check discovered.isOk
|
check discovered.isOk
|
||||||
check closest in discovered[]
|
check closest in discovered[]
|
||||||
|
@ -355,7 +363,7 @@ procSuite "Discovery v5 Tests":
|
||||||
# Request the target ENR and manually add it to the routing table.
|
# Request the target ENR and manually add it to the routing table.
|
||||||
# Ping for handshake based ENR passing will not work as our previous
|
# Ping for handshake based ENR passing will not work as our previous
|
||||||
# session will still be in the LRU cache.
|
# session will still be in the LRU cache.
|
||||||
let nodes = await mainNode.findNode(targetNode.localNode, 0)
|
let nodes = await mainNode.findNode(targetNode.localNode, @[0'u32])
|
||||||
check:
|
check:
|
||||||
nodes.isOk()
|
nodes.isOk()
|
||||||
nodes[].len == 1
|
nodes[].len == 1
|
||||||
|
@ -575,5 +583,5 @@ procSuite "Discovery v5 Tests":
|
||||||
recordInvalidDistance = enr.Record.init(
|
recordInvalidDistance = enr.Record.init(
|
||||||
1, pk, some(ValidIpAddress.init("12.13.14.15")), port, port)[]
|
1, pk, some(ValidIpAddress.init("12.13.14.15")), port, port)[]
|
||||||
records = [recordInvalidDistance]
|
records = [recordInvalidDistance]
|
||||||
test = verifyNodesRecords(records, fromNode, 0)
|
test = verifyNodesRecords(records, fromNode, 0'u32)
|
||||||
check test.len == 0
|
check test.len == 0
|
||||||
|
|
|
@ -26,9 +26,9 @@ suite "Discovery v5 Protocol Message Encodings":
|
||||||
|
|
||||||
test "FindNode Request":
|
test "FindNode Request":
|
||||||
var p: FindNodeMessage
|
var p: FindNodeMessage
|
||||||
p.distance = 0x0100
|
p.distances = @[0x0100'u32]
|
||||||
var reqId: RequestId = 1
|
var reqId: RequestId = 1
|
||||||
check encodeMessage(p, reqId).toHex == "03c401820100"
|
check encodeMessage(p, reqId).toHex == "03c501c3820100"
|
||||||
|
|
||||||
test "Nodes Response (empty)":
|
test "Nodes Response (empty)":
|
||||||
var p: NodesMessage
|
var p: NodesMessage
|
||||||
|
|
Loading…
Reference in New Issue