mirror of https://github.com/status-im/nim-eth.git
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:
parent
4e58eb48ce
commit
a339944bcf
|
@ -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()
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue