Some discv5 cleanup / refactor (#326)

* Use Address in HandshakeKey

* Refactor + comments + dcli use queryRandom

* Rename types.nim to messages.nim
This commit is contained in:
Kim De Mey 2021-02-02 22:47:21 +01:00 committed by GitHub
parent 4e58eb48ce
commit a339944bcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 61 additions and 42 deletions

View File

@ -166,6 +166,12 @@ proc setupNat(conf: DiscoveryConf): tuple[ip: Option[ValidIpAddress],
if extPorts.isSome: if extPorts.isSome:
(result.tcpPort, result.udpPort) = extPorts.get() (result.tcpPort, result.udpPort) = extPorts.get()
proc discover(d: protocol.Protocol) {.async.} =
while true:
let discovered = await d.queryRandom()
info "Lookup finished", nodes = discovered.len
await sleepAsync(30.seconds)
proc run(config: DiscoveryConf) = proc run(config: DiscoveryConf) =
let let
(ip, tcpPort, udpPort) = setupNat(config) (ip, tcpPort, udpPort) = setupNat(config)
@ -206,7 +212,7 @@ proc run(config: DiscoveryConf) =
echo "No Talk Response message returned" echo "No Talk Response message returned"
of noCommand: of noCommand:
d.start() d.start()
runForever() waitfor(discover(d))
when isMainModule: when isMainModule:
let config = DiscoveryConf.load() let config = DiscoveryConf.load()

View File

@ -1,7 +1,12 @@
## Discovery v5 packet encoding as specified at
## https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire.md#packet-encoding
## And handshake/sessions as specified at
## https://github.com/ethereum/devp2p/blob/master/discv5/discv5-theory.md#sessions
##
import import
std/[tables, options], std/[tables, options, hashes, net],
nimcrypto, stint, chronicles, bearssl, stew/[results, byteutils], nimcrypto, stint, chronicles, bearssl, stew/[results, byteutils],
eth/[rlp, keys], types, node, enr, hkdf, sessions eth/[rlp, keys], messages, node, enr, hkdf, sessions
from stew/objects import checkedEnumAssign from stew/objects import checkedEnumAssign
@ -68,6 +73,10 @@ type
node*: Option[Node] node*: Option[Node]
srcIdHs*: NodeId srcIdHs*: NodeId
HandshakeKey* = object
nodeId*: NodeId
address*: Address
Codec* = object Codec* = object
localNode*: Node localNode*: Node
privKey*: PrivateKey privKey*: PrivateKey
@ -76,6 +85,13 @@ type
DecodeResult*[T] = Result[T, cstring] DecodeResult*[T] = Result[T, cstring]
func `==`*(a, b: HandshakeKey): bool =
(a.nodeId == b.nodeId) and (a.address == b.address)
func hash*(key: HandshakeKey): Hash =
result = key.nodeId.hash !& key.address.hash
result = !$result
proc idHash(challengeData, ephkey: openarray[byte], nodeId: NodeId): proc idHash(challengeData, ephkey: openarray[byte], nodeId: NodeId):
MDigest[256] = MDigest[256] =
var ctx: sha256 var ctx: sha256
@ -238,7 +254,7 @@ proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec,
recordSeq: recordSeq, recordSeq: recordSeq,
challengeData: @iv & header) challengeData: @iv & header)
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey) challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
key = HandShakeKey(nodeId: toId, address: $toAddr) key = HandShakeKey(nodeId: toId, address: toAddr)
c.handshakes[key] = challenge c.handshakes[key] = challenge
@ -455,7 +471,7 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
if header.len < staticHeaderSize + authdataHeadSize + int(sigSize) + int(ephKeySize): if header.len < staticHeaderSize + authdataHeadSize + int(sigSize) + int(ephKeySize):
return err("Invalid header for handshake message packet") return err("Invalid header for handshake message packet")
let key = HandShakeKey(nodeId: srcId, address: $fromAddr) let key = HandShakeKey(nodeId: srcId, address: fromAddr)
var challenge: Challenge var challenge: Challenge
if not c.handshakes.pop(key, challenge): if not c.handshakes.pop(key, challenge):
return err("No challenge found: timed out or unsolicited packet") return err("No challenge found: timed out or unsolicited packet")

View File

@ -8,10 +8,10 @@ type
table: Table[K, DoublyLinkedNode[(K, V)]] # DoublyLinkedNode is alraedy ref table: Table[K, DoublyLinkedNode[(K, V)]] # DoublyLinkedNode is alraedy ref
capacity: int capacity: int
proc init*[K, V](T: type LRUCache[K, V], capacity: int): LRUCache[K, V] = func init*[K, V](T: type LRUCache[K, V], capacity: int): LRUCache[K, V] =
LRUCache[K, V](capacity: capacity) # Table and list init is done default LRUCache[K, V](capacity: capacity) # Table and list init is done default
proc get*[K, V](lru: var LRUCache[K, V], key: K): Option[V] = func get*[K, V](lru: var LRUCache[K, V], key: K): Option[V] =
let node = lru.table.getOrDefault(key, nil) let node = lru.table.getOrDefault(key, nil)
if node.isNil: if node.isNil:
return none(V) return none(V)
@ -20,7 +20,7 @@ proc get*[K, V](lru: var LRUCache[K, V], key: K): Option[V] =
lru.list.prepend(node) lru.list.prepend(node)
return some(node.value[1]) return some(node.value[1])
proc put*[K, V](lru: var LRUCache[K, V], key: K, value: V) = func put*[K, V](lru: var LRUCache[K, V], key: K, value: V) =
let node = lru.table.getOrDefault(key, nil) let node = lru.table.getOrDefault(key, nil)
if not node.isNil: if not node.isNil:
lru.list.remove(node) lru.list.remove(node)
@ -32,10 +32,10 @@ proc put*[K, V](lru: var LRUCache[K, V], key: K, value: V) =
lru.list.prepend((key, value)) lru.list.prepend((key, value))
lru.table[key] = lru.list.head lru.table[key] = lru.list.head
proc del*[K, V](lru: var LRUCache[K, V], key: K) = func del*[K, V](lru: var LRUCache[K, V], key: K) =
var node: DoublyLinkedNode[(K, V)] var node: DoublyLinkedNode[(K, V)]
if lru.table.pop(key, node): if lru.table.pop(key, node):
lru.list.remove(node) lru.list.remove(node)
proc len*[K, V](lru: LRUCache[K, V]): int = func len*[K, V](lru: LRUCache[K, V]): int =
lru.table.len lru.table.len

View File

@ -1,20 +1,15 @@
## Discovery v5 Protocol Messages as specified at
## https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire.md#protocol-messages
## These messages get RLP encoded.
##
import import
std/[hashes, net], std/[hashes, net],
stew/arrayops, stew/arrayops,
eth/rlp, enr, node eth/rlp, enr
{.push raises: [Defect].} {.push raises: [Defect].}
const
aesKeySize* = 128 div 8
type type
AesKey* = array[aesKeySize, byte]
HandshakeKey* = object
nodeId*: NodeId
address*: string # TODO: Replace with Address, need hash
MessageKind* = enum MessageKind* = enum
# TODO This is needed only to make Nim 1.2.6 happy # TODO This is needed only to make Nim 1.2.6 happy
# Without it, the `MessageKind` type cannot be used as # Without it, the `MessageKind` type cannot be used as
@ -139,7 +134,3 @@ proc append*(writer: var RlpWriter, ip: IpAddress) =
proc hash*(reqId: RequestId): Hash = proc hash*(reqId: RequestId): Hash =
hash(reqId.id) hash(reqId.id)
proc hash*(key: HandshakeKey): Hash =
result = key.nodeId.hash !& key.address.hash
result = !$result

View File

@ -48,7 +48,7 @@ func newNode*(r: Record): Result[Node, cstring] =
ok(Node(id: pk.get().toNodeId(), pubkey: pk.get(), record: r, ok(Node(id: pk.get().toNodeId(), pubkey: pk.get(), record: r,
address: none(Address))) address: none(Address)))
proc update*(n: Node, pk: PrivateKey, ip: Option[ValidIpAddress], func update*(n: Node, pk: PrivateKey, ip: Option[ValidIpAddress],
tcpPort, udpPort: Option[Port] = none[Port](), tcpPort, udpPort: Option[Port] = none[Port](),
extraFields: openarray[FieldPair] = []): Result[void, cstring] = extraFields: openarray[FieldPair] = []): Result[void, cstring] =
? n.record.update(pk, ip, tcpPort, udpPort, extraFields) ? n.record.update(pk, ip, tcpPort, udpPort, extraFields)

View File

@ -77,7 +77,7 @@ import
stew/shims/net as stewNet, json_serialization/std/net, stew/shims/net as stewNet, json_serialization/std/net,
stew/endians2, chronicles, chronos, stint, bearssl, metrics, stew/endians2, chronicles, chronos, stint, bearssl, metrics,
eth/[rlp, keys, async_utils], eth/[rlp, keys, async_utils],
types, encoding, node, routing_table, enr, random2, sessions, ip_vote messages, encoding, node, routing_table, enr, random2, sessions, ip_vote
import nimcrypto except toHex import nimcrypto except toHex
@ -341,7 +341,7 @@ proc handleMessage(d: Protocol, srcId: NodeId, fromAddr: Address,
proc sendWhoareyou(d: Protocol, toId: NodeId, a: Address, proc sendWhoareyou(d: Protocol, toId: NodeId, a: Address,
requestNonce: AESGCMNonce, node: Option[Node]) {.raises: [Exception].} = requestNonce: AESGCMNonce, node: Option[Node]) {.raises: [Exception].} =
let key = HandShakeKey(nodeId: toId, address: $a) let key = HandShakeKey(nodeId: toId, address: a)
if not d.codec.hasHandshake(key): if not d.codec.hasHandshake(key):
let let
recordSeq = if node.isSome(): node.get().record.seqNum recordSeq = if node.isSome(): node.get().record.seqNum

View File

@ -1,22 +1,28 @@
## Session cache as mentioned at
## https://github.com/ethereum/devp2p/blob/master/discv5/discv5-theory.md#session-cache
##
import import
std/options, std/options,
stint, stew/endians2, stew/shims/net, stint, stew/endians2, stew/shims/net,
types, node, lru node, lru
export lru export lru
{.push raises: [Defect].} {.push raises: [Defect].}
const keySize = sizeof(NodeId) + const
16 + # max size of ip address (ipv6) aesKeySize* = 128 div 8
2 # Sizeof port keySize = sizeof(NodeId) +
16 + # max size of ip address (ipv6)
2 # Sizeof port
type type
AesKey* = array[aesKeySize, byte]
SessionKey* = array[keySize, byte] SessionKey* = array[keySize, byte]
SessionValue* = array[sizeof(AesKey) + sizeof(AesKey), byte] SessionValue* = array[sizeof(AesKey) + sizeof(AesKey), byte]
Sessions* = LRUCache[SessionKey, SessionValue] Sessions* = LRUCache[SessionKey, SessionValue]
proc makeKey(id: NodeId, address: Address): SessionKey = func makeKey(id: NodeId, address: Address): SessionKey =
var pos = 0 var pos = 0
result[pos ..< pos+sizeof(id)] = toBytes(id) result[pos ..< pos+sizeof(id)] = toBytes(id)
pos.inc(sizeof(id)) pos.inc(sizeof(id))
@ -28,13 +34,13 @@ proc makeKey(id: NodeId, address: Address): SessionKey =
pos.inc(sizeof(address.ip.address_v6)) pos.inc(sizeof(address.ip.address_v6))
result[pos ..< pos+sizeof(address.port)] = toBytes(address.port.uint16) result[pos ..< pos+sizeof(address.port)] = toBytes(address.port.uint16)
proc store*(s: var Sessions, id: NodeId, address: Address, r, w: AesKey) = func store*(s: var Sessions, id: NodeId, address: Address, r, w: AesKey) =
var value: array[sizeof(r) + sizeof(w), byte] var value: array[sizeof(r) + sizeof(w), byte]
value[0 .. 15] = r value[0 .. 15] = r
value[16 .. ^1] = w value[16 .. ^1] = w
s.put(makeKey(id, address), value) s.put(makeKey(id, address), value)
proc load*(s: var Sessions, id: NodeId, address: Address, r, w: var AesKey): bool = func load*(s: var Sessions, id: NodeId, address: Address, r, w: var AesKey): bool =
let res = s.get(makeKey(id, address)) let res = s.get(makeKey(id, address))
if res.isSome(): if res.isSome():
let val = res.get() let val = res.get()
@ -44,5 +50,5 @@ proc load*(s: var Sessions, id: NodeId, address: Address, r, w: var AesKey): boo
else: else:
return false return false
proc del*(s: var Sessions, id: NodeId, address: Address) = func del*(s: var Sessions, id: NodeId, address: Address) =
s.del(makeKey(id, address)) s.del(makeKey(id, address))

View File

@ -1,6 +1,6 @@
import import
testutils/fuzzing, stew/byteutils, testutils/fuzzing, stew/byteutils,
eth/rlp, eth/p2p/discoveryv5/[encoding, types] eth/rlp, eth/p2p/discoveryv5/[encoding, messages]
test: test:
block: block:

View File

@ -2,7 +2,7 @@ import
std/tables, std/tables,
chronos, chronicles, stint, testutils/unittests, chronos, chronicles, stint, testutils/unittests,
stew/shims/net, eth/keys, bearssl, stew/shims/net, eth/keys, bearssl,
eth/p2p/discoveryv5/[enr, node, routing_table, encoding, sessions, types], eth/p2p/discoveryv5/[enr, node, routing_table, encoding, sessions, messages],
eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/protocol as discv5_protocol,
./discv5_test_helper ./discv5_test_helper
@ -610,7 +610,7 @@ procSuite "Discovery v5 Tests":
# Check handshake duplicates # Check handshake duplicates
check receiveNode.codec.handshakes.len == 1 check receiveNode.codec.handshakes.len == 1
# Check if it is for the first packet that a handshake is stored # Check if it is for the first packet that a handshake is stored
let key = HandShakeKey(nodeId: sendNode.id, address: $a) let key = HandShakeKey(nodeId: sendNode.id, address: a)
check receiveNode.codec.handshakes[key].whoareyouData.requestNonce == check receiveNode.codec.handshakes[key].whoareyouData.requestNonce ==
firstRequestNonce firstRequestNonce

View File

@ -2,7 +2,7 @@ import
std/[unittest, options, sequtils, tables], std/[unittest, options, sequtils, tables],
stint, stew/byteutils, stew/shims/net, stint, stew/byteutils, stew/shims/net,
eth/[rlp, keys], eth/[rlp, keys],
eth/p2p/discoveryv5/[types, encoding, enr, node, sessions] eth/p2p/discoveryv5/[messages, encoding, enr, node, sessions]
let rng = newRng() let rng = newRng()
@ -310,7 +310,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
challengeData: hexToSeqByte(whoareyouChallengeData)) challengeData: hexToSeqByte(whoareyouChallengeData))
pubkey = some(privKeyA.toPublicKey()) pubkey = some(privKeyA.toPublicKey())
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey) challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get())) key = HandShakeKey(nodeId: nodeA.id, address: nodeA.address.get())
check: not codecB.handshakes.hasKeyOrPut(key, challenge) check: not codecB.handshakes.hasKeyOrPut(key, challenge)
@ -357,7 +357,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
challengeData: hexToSeqByte(whoareyouChallengeData)) challengeData: hexToSeqByte(whoareyouChallengeData))
pubkey = none(PublicKey) pubkey = none(PublicKey)
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey) challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get())) key = HandShakeKey(nodeId: nodeA.id, address: nodeA.address.get())
check: not codecB.handshakes.hasKeyOrPut(key, challenge) check: not codecB.handshakes.hasKeyOrPut(key, challenge)
@ -472,7 +472,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
let decoded = codecB.decodePacket(nodeA.address.get(), data) let decoded = codecB.decodePacket(nodeA.address.get(), data)
let key = HandShakeKey(nodeId: nodeB.id, address: $nodeB.address.get()) let key = HandShakeKey(nodeId: nodeB.id, address: nodeB.address.get())
var challenge: Challenge var challenge: Challenge
check: check: