Log distance to uint16 and add public neighbours calls (#371)

* Use uint16 instead of uint32 for discv5 log distance

* Make neighboursAtDistances and neighbours calls available
This commit is contained in:
Kim De Mey 2021-07-13 10:05:46 +02:00 committed by GitHub
parent 41127eaee8
commit 79911ed5d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 65 additions and 57 deletions

View File

@ -79,7 +79,7 @@ type
distance* {. distance* {.
defaultValue: 255 defaultValue: 255
desc: "Distance parameter for the findNode message" desc: "Distance parameter for the findNode message"
name: "distance" .}: uint32 name: "distance" .}: uint16
# TODO: Order here matters as else the help message does not show all the # TODO: Order here matters as else the help message does not show all the
# information, see: https://github.com/status-im/nim-confutils/issues/15 # information, see: https://github.com/status-im/nim-confutils/issues/15
findNodeTarget* {. findNodeTarget* {.

View File

@ -47,7 +47,7 @@ type
port*: uint16 port*: uint16
FindNodeMessage* = object FindNodeMessage* = object
distances*: seq[uint32] distances*: seq[uint16]
NodesMessage* = object NodesMessage* = object
total*: uint32 total*: uint32

View File

@ -31,6 +31,8 @@ type
func toNodeId*(pk: PublicKey): NodeId = func toNodeId*(pk: PublicKey): NodeId =
## Convert public key to a node identifier. ## Convert public key to a node identifier.
# Keccak256 hash is used as defined in ENR spec for scheme v4:
# https://github.com/ethereum/devp2p/blob/master/enr.md#v4-identity-scheme
readUintBE[256](keccak256.digest(pk.toRaw()).data) readUintBE[256](keccak256.digest(pk.toRaw()).data)
func newNode*(r: Record): Result[Node, cstring] = func newNode*(r: Record): Result[Node, cstring] =

View File

@ -197,9 +197,15 @@ proc randomNodes*(d: Protocol, maxAmount: int,
## the nodes selected are filtered by provided `enrField`. ## the nodes selected are filtered by provided `enrField`.
d.randomNodes(maxAmount, proc(x: Node): bool = x.record.contains(enrField)) d.randomNodes(maxAmount, proc(x: Node): bool = x.record.contains(enrField))
proc neighbours*(d: Protocol, id: NodeId, k: int = BUCKET_SIZE): seq[Node] = proc neighbours*(d: Protocol, id: NodeId, k: int = BUCKET_SIZE,
seenOnly = false): seq[Node] =
## Return up to k neighbours (closest node ids) of the given node id. ## Return up to k neighbours (closest node ids) of the given node id.
d.routingTable.neighbours(id, k) d.routingTable.neighbours(id, k, seenOnly)
proc neighboursAtDistances*(d: Protocol, distances: seq[uint16],
k: int = BUCKET_SIZE, seenOnly = false): seq[Node] =
## Return up to k neighbours (closest node ids) at given distances.
d.routingTable.neighboursAtDistances(distances, k, seenOnly)
proc nodesDiscovered*(d: Protocol): int = d.routingTable.len proc nodesDiscovered*(d: Protocol): int = d.routingTable.len
@ -293,7 +299,7 @@ proc handleFindNode(d: Protocol, fromId: NodeId, fromAddr: Address,
d.sendNodes(fromId, fromAddr, reqId, [d.localNode]) d.sendNodes(fromId, fromAddr, reqId, [d.localNode])
else: else:
# TODO: Still deduplicate also? # TODO: Still deduplicate also?
if fn.distances.all(proc (x: uint32): bool = return x <= 256): if fn.distances.all(proc (x: uint16): bool = return x <= 256):
d.sendNodes(fromId, fromAddr, reqId, d.sendNodes(fromId, fromAddr, reqId,
d.routingTable.neighboursAtDistances(fn.distances, seenOnly = true)) d.routingTable.neighboursAtDistances(fn.distances, seenOnly = true))
else: else:
@ -491,7 +497,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,
distances: varargs[uint32]): seq[Node] = distances: varargs[uint16]): seq[Node] =
## 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.
@ -609,7 +615,7 @@ proc ping*(d: Protocol, toNode: Node):
discovery_message_requests_outgoing.inc(labelValues = ["no_response"]) discovery_message_requests_outgoing.inc(labelValues = ["no_response"])
return err("Pong message not received in time") return err("Pong message not received in time")
proc findNode*(d: Protocol, toNode: Node, distances: seq[uint32]): proc findNode*(d: Protocol, toNode: Node, distances: seq[uint16]):
Future[DiscResult[seq[Node]]] {.async.} = Future[DiscResult[seq[Node]]] {.async.} =
## Send a discovery findNode message. ## Send a discovery findNode message.
## ##
@ -648,14 +654,14 @@ proc talkreq*(d: Protocol, toNode: Node, protocol, request: seq[byte]):
discovery_message_requests_outgoing.inc(labelValues = ["no_response"]) discovery_message_requests_outgoing.inc(labelValues = ["no_response"])
return err("Talk response message not received in time") return err("Talk response message not received in time")
proc lookupDistances(target, dest: NodeId): seq[uint32] = proc lookupDistances(target, dest: NodeId): seq[uint16] =
let td = logDist(target, dest) let td = logDist(target, dest)
result.add(td) result.add(td)
var i = 1'u32 var i = 1'u16
while result.len < lookupRequestLimit: while result.len < lookupRequestLimit:
if td + i < 256: if td + i < 256:
result.add(td + i) result.add(td + i)
if td - i > 0'u32: if td - i > 0'u16:
result.add(td - i) result.add(td - i)
inc i inc i
@ -804,7 +810,7 @@ proc resolve*(d: Protocol, id: NodeId): Future[Option[Node]] {.async.} =
let node = d.getNode(id) let node = d.getNode(id)
if node.isSome(): if node.isSome():
let request = await d.findNode(node.get(), @[0'u32]) let request = await d.findNode(node.get(), @[0'u16])
# 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:
@ -853,7 +859,7 @@ proc revalidateNode*(d: Protocol, n: Node) {.async.} =
let res = pong.get() let res = pong.get()
if res.enrSeq > n.record.seqNum: if res.enrSeq > n.record.seqNum:
# Request new ENR # Request new ENR
let nodes = await d.findNode(n, @[0'u32]) let nodes = await d.findNode(n, @[0'u16])
if nodes.isOk() and nodes[].len > 0: if nodes.isOk() and nodes[].len > 0:
discard d.addNode(nodes[][0]) discard d.addNode(nodes[][0])

View File

@ -98,7 +98,7 @@ 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
proc logDist*(a, b: NodeId): uint32 = proc logDist*(a, b: NodeId): uint16 =
## Calculate the logarithmic distance between two `NodeId`s. ## Calculate the logarithmic distance between two `NodeId`s.
## ##
## According the specification, this is the log base 2 of the distance. But it ## According the specification, this is the log base 2 of the distance. But it
@ -115,7 +115,7 @@ proc logDist*(a, b: NodeId): uint32 =
else: else:
lz += bitops.countLeadingZeroBits(x) lz += bitops.countLeadingZeroBits(x)
break break
return uint32(a.len * 8 - lz) return uint16(a.len * 8 - lz)
proc newKBucket(istart, iend: NodeId, bucketIpLimit: uint): KBucket = proc newKBucket(istart, iend: NodeId, bucketIpLimit: uint): KBucket =
result.new() result.new()
@ -421,7 +421,7 @@ proc neighbours*(r: RoutingTable, id: NodeId, k: int = BUCKET_SIZE,
if result.len > k: if result.len > k:
result.setLen(k) result.setLen(k)
proc idAtDistance*(id: NodeId, dist: uint32): NodeId = proc idAtDistance*(id: NodeId, dist: uint16): NodeId =
## Calculate the "lowest" `NodeId` for given logarithmic distance. ## Calculate the "lowest" `NodeId` for given logarithmic distance.
## A logarithmic distance obviously covers a whole range of distances and thus ## A logarithmic distance obviously covers a whole range of distances and thus
## potential `NodeId`s. ## potential `NodeId`s.
@ -429,7 +429,7 @@ proc idAtDistance*(id: NodeId, dist: uint32): NodeId =
# zeroes and xor those` with the id. # zeroes and xor those` with the id.
id xor (1.stuint(256) shl (dist.int - 1)) id xor (1.stuint(256) shl (dist.int - 1))
proc neighboursAtDistance*(r: RoutingTable, distance: uint32, proc neighboursAtDistance*(r: RoutingTable, distance: uint16,
k: int = BUCKET_SIZE, seenOnly = false): seq[Node] = k: int = BUCKET_SIZE, seenOnly = false): seq[Node] =
## Return up to k neighbours at given logarithmic distance. ## Return up to k neighbours at given logarithmic distance.
result = r.neighbours(idAtDistance(r.thisNode.id, distance), k, seenOnly) result = r.neighbours(idAtDistance(r.thisNode.id, distance), k, seenOnly)
@ -437,7 +437,7 @@ 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], proc neighboursAtDistances*(r: RoutingTable, distances: seq[uint16],
k: int = BUCKET_SIZE, seenOnly = false): seq[Node] = k: int = BUCKET_SIZE, seenOnly = false): seq[Node] =
## Return up to k neighbours at given logarithmic distances. ## Return up to k neighbours at given logarithmic distances.
# TODO: This will currently return nodes with neighbouring distances on the # TODO: This will currently return nodes with neighbouring distances on the

View File

@ -62,19 +62,19 @@ procSuite "Discovery v5 Tests":
const const
targetId = "0x0000" targetId = "0x0000"
testValues = [ testValues = [
("0x0001", 1'u32), ("0x0001", 1'u16),
("0x0002", 2'u32), ("0x0002", 2'u16),
("0x0003", 2'u32), ("0x0003", 2'u16),
("0x0004", 3'u32), ("0x0004", 3'u16),
("0x0007", 3'u32), ("0x0007", 3'u16),
("0x0008", 4'u32), ("0x0008", 4'u16),
("0x000f", 4'u32), ("0x000f", 4'u16),
("0x0080", 8'u32), ("0x0080", 8'u16),
("0x00ff", 8'u32), ("0x00ff", 8'u16),
("0x0100", 9'u32), ("0x0100", 9'u16),
("0x01ff", 9'u32), ("0x01ff", 9'u16),
("0x8000", 16'u32), ("0x8000", 16'u16),
("0xffff", 16'u32) ("0xffff", 16'u16)
] ]
for (id, d) in testValues: for (id, d) in testValues:
@ -84,12 +84,12 @@ procSuite "Discovery v5 Tests":
const const
targetKey = "5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125" targetKey = "5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"
testValues = [ testValues = [
("29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169", 251'u32), ("29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169", 251'u16),
("1c9b1cafbec00848d2c174b858219914b42a7d5c9359b1ca03fd650e8239ae94", 252'u32), ("1c9b1cafbec00848d2c174b858219914b42a7d5c9359b1ca03fd650e8239ae94", 252'u16),
("2d0511ae9bf590166597eeab86b6f27b1ab761761eaea8965487b162f8703847", 253'u32), ("2d0511ae9bf590166597eeab86b6f27b1ab761761eaea8965487b162f8703847", 253'u16),
("dec742079ec00ff4ec1284d7905bc3de2366f67a0769431fd16f80fd68c58a7c", 254'u32), ("dec742079ec00ff4ec1284d7905bc3de2366f67a0769431fd16f80fd68c58a7c", 254'u16),
("da8645f90826e57228d9ea72aff84500060ad111a5d62e4af831ed8e4b5acfb8", 255'u32), ("da8645f90826e57228d9ea72aff84500060ad111a5d62e4af831ed8e4b5acfb8", 255'u16),
("8c5b422155d33ea8e9d46f71d1ad3e7b24cb40051413ffa1a81cff613d243ba9", 256'u32) ("8c5b422155d33ea8e9d46f71d1ad3e7b24cb40051413ffa1a81cff613d243ba9", 256'u16)
] ]
let targetId = toNodeId(PublicKey.fromHex(targetKey)[]) let targetId = toNodeId(PublicKey.fromHex(targetKey)[])
@ -102,13 +102,13 @@ procSuite "Discovery v5 Tests":
const const
targetId = "0x0000" targetId = "0x0000"
testValues = [ # possible id in that distance range testValues = [ # possible id in that distance range
("0x0001", 1'u32), ("0x0001", 1'u16),
("0x0002", 2'u32), ("0x0002", 2'u16),
("0x0004", 3'u32), ("0x0004", 3'u16),
("0x0008", 4'u32), ("0x0008", 4'u16),
("0x0080", 8'u32), ("0x0080", 8'u16),
("0x0100", 9'u32), ("0x0100", 9'u16),
("0x8000", 16'u32) ("0x8000", 16'u16)
] ]
for (id, d) in testValues: for (id, d) in testValues:
@ -118,12 +118,12 @@ procSuite "Discovery v5 Tests":
const const
targetKey = "5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125" targetKey = "5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"
testValues = [ # possible id in that distance range testValues = [ # possible id in that distance range
("9e5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 251'u32), ("9e5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 251'u16),
("925b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 252'u32), ("925b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 252'u16),
("8a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 253'u32), ("8a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 253'u16),
("ba5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 254'u32), ("ba5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 254'u16),
("da5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 255'u32), ("da5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 255'u16),
("1a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 256'u32) ("1a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 256'u16)
] ]
let targetId = toNodeId(PublicKey.fromHex(targetKey)[]) let targetId = toNodeId(PublicKey.fromHex(targetKey)[])
@ -132,7 +132,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'u32 const dist = 253'u16
let let
mainNodeKey = PrivateKey.fromHex( mainNodeKey = PrivateKey.fromHex(
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[] "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
@ -152,7 +152,7 @@ procSuite "Discovery v5 Tests":
# Get ENR of the node itself # Get ENR of the node itself
var discovered = var discovered =
await findNode(testNode, mainNode.localNode, @[0'u32]) await findNode(testNode, mainNode.localNode, @[0'u16])
check: check:
discovered.isOk discovered.isOk
discovered[].len == 1 discovered[].len == 1
@ -167,14 +167,14 @@ procSuite "Discovery v5 Tests":
# Too high logarithmic distance, should return no nodes. # Too high logarithmic distance, should return no nodes.
discovered = discovered =
await findNode(testNode, mainNode.localNode, @[4294967295'u32]) await findNode(testNode, mainNode.localNode, @[high(uint16)])
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 findNode(testNode, mainNode.localNode, @[256'u32]) await findNode(testNode, mainNode.localNode, @[256'u16])
check: check:
discovered.isOk discovered.isOk
discovered[].len == 1 discovered[].len == 1
@ -182,7 +182,7 @@ procSuite "Discovery v5 Tests":
# Empty bucket # Empty bucket
discovered = discovered =
await findNode(testNode, mainNode.localNode, @[254'u32]) await findNode(testNode, mainNode.localNode, @[254'u16])
check discovered.isOk check discovered.isOk
check discovered[].len == 0 check discovered[].len == 0
@ -297,7 +297,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'u32]) let nodes = await mainNode.findNode(targetNode.localNode, @[0'u16])
check: check:
nodes.isOk() nodes.isOk()
nodes[].len == 1 nodes[].len == 1
@ -524,7 +524,7 @@ procSuite "Discovery v5 Tests":
1, pk, some(ValidIpAddress.init("12.13.14.15")), 1, pk, some(ValidIpAddress.init("12.13.14.15")),
some(port), some(port))[] some(port), some(port))[]
records = [recordInvalidDistance] records = [recordInvalidDistance]
test = verifyNodesRecords(records, fromNode, 0'u32) test = verifyNodesRecords(records, fromNode, 0'u16)
check test.len == 0 check test.len == 0
asyncTest "Handshake cleanup: different ids": asyncTest "Handshake cleanup: different ids":

View File

@ -51,7 +51,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
test "FindNode Request": test "FindNode Request":
let let
distances = @[0x0100'u32] distances = @[0x0100'u16]
fn = FindNodeMessage(distances: distances) fn = FindNodeMessage(distances: distances)
reqId = RequestId(id: @[1.byte]) reqId = RequestId(id: @[1.byte])