2021-04-28 14:20:05 +00:00
|
|
|
# nim-eth - Node Discovery Protocol v5
|
2023-05-10 13:50:04 +00:00
|
|
|
# Copyright (c) 2020-2023 Status Research & Development GmbH
|
2021-04-28 14:20:05 +00:00
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
|
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2023-05-10 13:50:04 +00:00
|
|
|
{.push raises: [].}
|
2021-04-28 14:20:05 +00:00
|
|
|
|
2020-02-17 16:44:56 +00:00
|
|
|
import
|
2020-07-12 15:25:18 +00:00
|
|
|
std/hashes,
|
2022-09-05 09:09:38 +00:00
|
|
|
nimcrypto/[keccak], stint, chronos, stew/shims/net, chronicles,
|
2021-12-06 14:24:07 +00:00
|
|
|
../../keys, ../../net/utils,
|
|
|
|
./enr
|
2019-12-16 19:38:45 +00:00
|
|
|
|
2021-01-21 16:21:36 +00:00
|
|
|
export stint
|
|
|
|
|
2019-12-16 19:38:45 +00:00
|
|
|
type
|
2020-05-28 08:19:36 +00:00
|
|
|
NodeId* = UInt256
|
|
|
|
|
|
|
|
Address* = object
|
2020-06-09 09:09:35 +00:00
|
|
|
ip*: ValidIpAddress
|
2020-05-28 08:19:36 +00:00
|
|
|
port*: Port
|
|
|
|
|
2019-12-16 19:38:45 +00:00
|
|
|
Node* = ref object
|
|
|
|
id*: NodeId
|
2020-05-28 08:19:36 +00:00
|
|
|
pubkey*: PublicKey
|
|
|
|
address*: Option[Address]
|
2019-12-16 19:38:45 +00:00
|
|
|
record*: Record
|
2020-06-30 11:35:15 +00:00
|
|
|
seen*: bool ## Indicates if there was at least one successful
|
|
|
|
## request-response with this node.
|
2019-12-16 19:38:45 +00:00
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func toNodeId*(pk: PublicKey): NodeId =
|
2020-07-12 15:25:18 +00:00
|
|
|
## Convert public key to a node identifier.
|
2021-07-13 08:05:46 +00:00
|
|
|
# Keccak256 hash is used as defined in ENR spec for scheme v4:
|
|
|
|
# https://github.com/ethereum/devp2p/blob/master/enr.md#v4-identity-scheme
|
2020-04-04 16:44:01 +00:00
|
|
|
readUintBE[256](keccak256.digest(pk.toRaw()).data)
|
2019-12-16 19:38:45 +00:00
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func newNode*(r: Record): Result[Node, cstring] =
|
2020-07-12 15:25:18 +00:00
|
|
|
## Create a new `Node` from a `Record`.
|
2019-12-16 19:38:45 +00:00
|
|
|
# TODO: Handle IPv6
|
|
|
|
|
2020-04-29 22:11:03 +00:00
|
|
|
let pk = r.get(PublicKey)
|
2020-07-12 15:25:18 +00:00
|
|
|
# This check is redundant for a properly created record as the deserialization
|
|
|
|
# of a record will fail at `verifySignature` if there is no public key.
|
2020-04-29 22:11:03 +00:00
|
|
|
if pk.isNone():
|
2020-05-28 08:19:36 +00:00
|
|
|
return err("Could not recover public key from ENR")
|
2020-02-12 13:36:39 +00:00
|
|
|
|
2020-07-12 15:25:18 +00:00
|
|
|
# Also this can not fail for a properly created record as id is checked upon
|
|
|
|
# deserialization.
|
2020-05-28 08:19:36 +00:00
|
|
|
let tr = ? r.toTypedRecord()
|
|
|
|
if tr.ip.isSome() and tr.udp.isSome():
|
2020-06-11 19:24:52 +00:00
|
|
|
let a = Address(ip: ipv4(tr.ip.get()), port: Port(tr.udp.get()))
|
2020-05-28 08:19:36 +00:00
|
|
|
|
|
|
|
ok(Node(id: pk.get().toNodeId(), pubkey: pk.get() , record: r,
|
|
|
|
address: some(a)))
|
|
|
|
else:
|
|
|
|
ok(Node(id: pk.get().toNodeId(), pubkey: pk.get(), record: r,
|
|
|
|
address: none(Address)))
|
2019-12-16 19:38:45 +00:00
|
|
|
|
2021-02-02 21:47:21 +00:00
|
|
|
func update*(n: Node, pk: PrivateKey, ip: Option[ValidIpAddress],
|
2021-01-26 13:11:22 +00:00
|
|
|
tcpPort, udpPort: Option[Port] = none[Port](),
|
2021-12-20 12:14:50 +00:00
|
|
|
extraFields: openArray[FieldPair] = []): Result[void, cstring] =
|
2020-11-26 17:20:15 +00:00
|
|
|
? n.record.update(pk, ip, tcpPort, udpPort, extraFields)
|
|
|
|
|
|
|
|
if ip.isSome():
|
2021-01-26 13:11:22 +00:00
|
|
|
if udpPort.isSome():
|
|
|
|
let a = Address(ip: ip.get(), port: udpPort.get())
|
|
|
|
n.address = some(a)
|
|
|
|
elif n.address.isSome():
|
|
|
|
let a = Address(ip: ip.get(), port: n.address.get().port)
|
|
|
|
n.address = some(a)
|
|
|
|
else:
|
|
|
|
n.address = none(Address)
|
2020-11-26 17:20:15 +00:00
|
|
|
else:
|
|
|
|
n.address = none(Address)
|
|
|
|
|
|
|
|
ok()
|
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func hash*(n: Node): hashes.Hash = hash(n.pubkey.toRaw)
|
2021-01-21 16:21:36 +00:00
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func `==`*(a, b: Node): bool =
|
2020-04-29 22:11:03 +00:00
|
|
|
(a.isNil and b.isNil) or
|
2020-05-28 08:19:36 +00:00
|
|
|
(not a.isNil and not b.isNil and a.pubkey == b.pubkey)
|
2019-12-18 10:36:11 +00:00
|
|
|
|
2021-12-06 14:24:07 +00:00
|
|
|
func hash*(id: NodeId): Hash =
|
|
|
|
hash(id.toByteArrayBE)
|
|
|
|
|
2022-06-17 20:45:37 +00:00
|
|
|
proc random*(T: type NodeId, rng: var HmacDrbgContext): T =
|
|
|
|
rng.generate(T)
|
2021-01-21 16:21:36 +00:00
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func `$`*(id: NodeId): string =
|
2020-07-15 13:27:22 +00:00
|
|
|
id.toHex()
|
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func shortLog*(id: NodeId): string =
|
|
|
|
## Returns compact string representation of ``id``.
|
|
|
|
var sid = $id
|
|
|
|
if len(sid) <= 10:
|
|
|
|
result = sid
|
|
|
|
else:
|
|
|
|
result = newStringOfCap(10)
|
|
|
|
for i in 0..<2:
|
|
|
|
result.add(sid[i])
|
|
|
|
result.add("*")
|
|
|
|
for i in (len(sid) - 6)..sid.high:
|
|
|
|
result.add(sid[i])
|
|
|
|
|
2021-01-21 16:21:36 +00:00
|
|
|
func hash*(a: Address): hashes.Hash =
|
2021-12-06 14:24:07 +00:00
|
|
|
let res = a.ip.hash !& a.port.hash
|
|
|
|
!$res
|
2021-01-21 16:21:36 +00:00
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func `$`*(a: Address): string =
|
2020-05-28 08:19:36 +00:00
|
|
|
result.add($a.ip)
|
|
|
|
result.add(":" & $a.port)
|
2020-03-18 14:27:26 +00:00
|
|
|
|
2020-10-23 14:41:44 +00:00
|
|
|
func shortLog*(n: Node): string =
|
|
|
|
if n.isNil:
|
|
|
|
"uninitialized"
|
2020-05-28 08:19:36 +00:00
|
|
|
elif n.address.isNone():
|
2020-10-23 14:41:44 +00:00
|
|
|
shortLog(n.id) & ":unaddressable"
|
2019-12-16 19:38:45 +00:00
|
|
|
else:
|
2020-10-23 14:41:44 +00:00
|
|
|
shortLog(n.id) & ":" & $n.address.get()
|
|
|
|
|
|
|
|
func shortLog*(nodes: seq[Node]): string =
|
|
|
|
result = "["
|
|
|
|
|
|
|
|
var first = true
|
|
|
|
for n in nodes:
|
|
|
|
if first:
|
|
|
|
first = false
|
|
|
|
else:
|
|
|
|
result.add(", ")
|
|
|
|
result.add(shortLog(n))
|
|
|
|
|
|
|
|
result.add("]")
|
2022-11-10 09:52:24 +00:00
|
|
|
|
|
|
|
chronicles.formatIt(NodeId): shortLog(it)
|
|
|
|
chronicles.formatIt(Address): $it
|
|
|
|
chronicles.formatIt(Node): shortLog(it)
|
2020-10-23 14:41:44 +00:00
|
|
|
chronicles.formatIt(seq[Node]): shortLog(it)
|