mirror of
https://github.com/codex-storage/nim-codex-dht.git
synced 2025-02-10 19:06:36 +00:00
feat: Swap ENR to libp2p SignedPeerRecords
Swap all instances of Record with SignedPeerRecord. Allow for `SignedPeerRecord`s to be updated by updating the first multiaddress in the `PeerRecord`. This also increments the `seqNo` in the `PeerRecord` only if the address was actually updated.
This commit is contained in:
parent
b843c8823c
commit
9a01791e11
@ -14,7 +14,7 @@ requires "nim >= 1.2.0",
|
||||
"chronicles >= 0.10.2 & < 0.11.0",
|
||||
"chronos >= 3.0.11 & < 3.1.0",
|
||||
"eth >= 1.0.0 & < 1.1.0", # to be removed in https://github.com/status-im/nim-libp2p-dht/issues/2
|
||||
"libp2p#22fe39819ae8b3118a59e3962ea42087f878c5b6",
|
||||
"libp2p#c7504d2446717a48a79c8b15e0f21bbfc84957ba",
|
||||
"metrics",
|
||||
"protobufserialization >= 0.2.0 & < 0.3.0",
|
||||
"secp256k1 >= 0.5.2 & < 0.6.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import
|
||||
./discv5/[enr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport]
|
||||
./discv5/[spr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport]
|
||||
|
||||
export enr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport
|
||||
export spr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport
|
@ -1,4 +0,0 @@
|
||||
import
|
||||
../private/eth/p2p/discoveryv5/enr
|
||||
|
||||
export enr
|
4
libp2pdht/discv5/spr.nim
Normal file
4
libp2pdht/discv5/spr.nim
Normal file
@ -0,0 +1,4 @@
|
||||
import
|
||||
../private/eth/p2p/discoveryv5/spr
|
||||
|
||||
export spr
|
@ -17,7 +17,8 @@ import
|
||||
std/[tables, options, hashes, net],
|
||||
nimcrypto, stint, chronicles, bearssl, stew/[results, byteutils], metrics,
|
||||
eth/[rlp, keys],
|
||||
"."/[messages, messages_encoding, node, enr, hkdf, sessions]
|
||||
libp2p/signed_envelope,
|
||||
"."/[messages, messages_encoding, node, spr, hkdf, sessions]
|
||||
|
||||
from stew/objects import checkedEnumAssign
|
||||
|
||||
@ -56,7 +57,7 @@ type
|
||||
|
||||
Challenge* = object
|
||||
whoareyouData*: WhoareyouData
|
||||
pubkey*: Option[PublicKey]
|
||||
pubkey*: Option[keys.PublicKey]
|
||||
|
||||
StaticHeader* = object
|
||||
flag: Flag
|
||||
@ -92,7 +93,7 @@ type
|
||||
|
||||
Codec* = object
|
||||
localNode*: Node
|
||||
privKey*: PrivateKey
|
||||
privKey*: keys.PrivateKey
|
||||
handshakes*: Table[HandshakeKey, Challenge]
|
||||
sessions*: Sessions
|
||||
|
||||
@ -116,16 +117,16 @@ proc idHash(challengeData, ephkey: openArray[byte], nodeId: NodeId):
|
||||
result = ctx.finish()
|
||||
ctx.clear()
|
||||
|
||||
proc createIdSignature*(privKey: PrivateKey, challengeData,
|
||||
proc createIdSignature*(privKey: keys.PrivateKey, challengeData,
|
||||
ephKey: openArray[byte], nodeId: NodeId): SignatureNR =
|
||||
signNR(privKey, SkMessage(idHash(challengeData, ephKey, nodeId).data))
|
||||
|
||||
proc verifyIdSignature*(sig: SignatureNR, challengeData, ephKey: openArray[byte],
|
||||
nodeId: NodeId, pubkey: PublicKey): bool =
|
||||
nodeId: NodeId, pubkey: keys.PublicKey): bool =
|
||||
let h = idHash(challengeData, ephKey, nodeId)
|
||||
verify(sig, SkMessage(h.data), pubkey)
|
||||
|
||||
proc deriveKeys*(n1, n2: NodeId, priv: PrivateKey, pub: PublicKey,
|
||||
proc deriveKeys*(n1, n2: NodeId, priv: keys.PrivateKey, pub: keys.PublicKey,
|
||||
challengeData: openArray[byte]): HandshakeSecrets =
|
||||
let eph = ecdhRawFull(priv, pub)
|
||||
|
||||
@ -235,7 +236,7 @@ proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
|
||||
proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
toId: NodeId, toAddr: Address, requestNonce: AESGCMNonce, recordSeq: uint64,
|
||||
pubkey: Option[PublicKey]): seq[byte] =
|
||||
pubkey: Option[keys.PublicKey]): seq[byte] =
|
||||
var idNonce: IdNonce
|
||||
brHmacDrbgGenerate(rng, idNonce)
|
||||
|
||||
@ -277,7 +278,7 @@ proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
|
||||
proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
toId: NodeId, toAddr: Address, message: openArray[byte],
|
||||
whoareyouData: WhoareyouData, pubkey: PublicKey): seq[byte] =
|
||||
whoareyouData: WhoareyouData, pubkey: keys.PublicKey): seq[byte] =
|
||||
var header: seq[byte]
|
||||
var nonce: AESGCMNonce
|
||||
brHmacDrbgGenerate(rng, nonce)
|
||||
@ -292,7 +293,7 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
authdataHead.add(33'u8) # eph-key-size: 33
|
||||
authdata.add(authdataHead)
|
||||
|
||||
let ephKeys = KeyPair.random(rng)
|
||||
let ephKeys = keys.KeyPair.random(rng)
|
||||
let signature = createIdSignature(c.privKey, whoareyouData.challengeData,
|
||||
ephKeys.pubkey.toRawCompressed(), toId)
|
||||
|
||||
@ -300,9 +301,15 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
# compressed pub key format (33 bytes)
|
||||
authdata.add(ephKeys.pubkey.toRawCompressed())
|
||||
|
||||
# Add ENR of sequence number is newer
|
||||
# Add SPR of sequence number is newer
|
||||
if whoareyouData.recordSeq < c.localNode.record.seqNum:
|
||||
authdata.add(encode(c.localNode.record))
|
||||
let encoded = c.localNode.record.encode
|
||||
if encoded.isOk:
|
||||
trace "Encoded local node's SignedPeerRecord", bytes = encoded.get
|
||||
authdata.add(encoded.get)
|
||||
else:
|
||||
error "Failed to encode local node's SignedPeerRecord", error = encoded.error
|
||||
authdata.add(@[])
|
||||
|
||||
let secrets = deriveKeys(c.localNode.id, toId, ephKeys.seckey, pubkey,
|
||||
whoareyouData.challengeData)
|
||||
@ -312,6 +319,7 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||
authdata.len())
|
||||
|
||||
header.add(staticHeader)
|
||||
trace "Handshake packet's authdata", authdata
|
||||
header.add(authdata)
|
||||
|
||||
c.sessions.store(toId, toAddr, secrets.recipientKey, secrets.initiatorKey)
|
||||
@ -446,7 +454,7 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||
sigSize = uint8(authdata[32])
|
||||
ephKeySize = uint8(authdata[33])
|
||||
|
||||
# If smaller, as it can be equal and bigger (in case it holds an enr)
|
||||
# If smaller, as it can be equal and bigger (in case it holds an spr)
|
||||
if header.len < staticHeaderSize + authdataHeadSize + int(sigSize) + int(ephKeySize):
|
||||
return err("Invalid header for handshake message packet")
|
||||
|
||||
@ -461,40 +469,44 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||
let
|
||||
ephKeyPos = authdataHeadSize + int(sigSize)
|
||||
ephKeyRaw = authdata[ephKeyPos..<ephKeyPos + int(ephKeySize)]
|
||||
ephKey = ? PublicKey.fromRaw(ephKeyRaw)
|
||||
ephKey = ? keys.PublicKey.fromRaw(ephKeyRaw)
|
||||
|
||||
var record: Option[enr.Record]
|
||||
var record: Option[SignedPeerRecord]
|
||||
let recordPos = ephKeyPos + int(ephKeySize)
|
||||
if authdata.len() > recordPos:
|
||||
# There is possibly an ENR still
|
||||
# There is possibly an SPR still
|
||||
try:
|
||||
trace "Decoding handshake packet's authdata", authdata, recordPos, decodeBytes = authdata.toOpenArray(recordPos, authdata.high)
|
||||
# Signature check of record happens in decode.
|
||||
record = some(rlp.decode(authdata.toOpenArray(recordPos, authdata.high),
|
||||
enr.Record))
|
||||
let
|
||||
prBytes = @(authdata.toOpenArray(recordPos, authdata.high))
|
||||
decoded = SignedPeerRecord.decode(prBytes)
|
||||
.expect("Should be valid bytes for SignedPeerRecord")
|
||||
record = some(decoded)
|
||||
except RlpError, ValueError:
|
||||
return err("Invalid encoded ENR")
|
||||
return err("Invalid encoded SPR")
|
||||
|
||||
var pubkey: PublicKey
|
||||
var pubkey: keys.PublicKey
|
||||
var newNode: Option[Node]
|
||||
# TODO: Shall we return Node or Record? Record makes more sense, but we do
|
||||
# need the pubkey and the nodeid
|
||||
# TODO: Shall we return Node or SignedPeerRecord? SignedPeerRecord makes
|
||||
# more sense, but we do need the pubkey and the nodeid
|
||||
if record.isSome():
|
||||
# Node returned might not have an address or not a valid address.
|
||||
let node = ? newNode(record.get())
|
||||
if node.id != srcId:
|
||||
return err("Invalid node id: does not match node id of ENR")
|
||||
return err("Invalid node id: does not match node id of SPR")
|
||||
|
||||
# Note: Not checking if the record seqNum is higher than the one we might
|
||||
# have stored as it comes from this node directly.
|
||||
pubkey = node.pubkey
|
||||
newNode = some(node)
|
||||
else:
|
||||
# TODO: Hmm, should we still verify node id of the ENR of this node?
|
||||
# TODO: Hmm, should we still verify node id of the SPR of this node?
|
||||
if challenge.pubkey.isSome():
|
||||
pubkey = challenge.pubkey.get()
|
||||
else:
|
||||
# We should have received a Record in this case.
|
||||
return err("Missing ENR in handshake packet")
|
||||
# We should have received a SignedPeerRecord in this case.
|
||||
return err("Missing SPR in handshake packet")
|
||||
|
||||
# Verify the id-signature
|
||||
let sig = ? SignatureNR.fromRaw(
|
||||
|
@ -1,528 +0,0 @@
|
||||
# nim-eth - Node Discovery Protocol v5
|
||||
# Copyright (c) 2020-2021 Status Research & Development GmbH
|
||||
# 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.
|
||||
#
|
||||
## ENR implementation according to specification in EIP-778:
|
||||
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-778.md
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
std/[strutils, macros, algorithm, options],
|
||||
stew/shims/net, stew/[base64, results], nimcrypto,
|
||||
eth/[rlp, keys]
|
||||
|
||||
export options, results
|
||||
|
||||
const
|
||||
maxEnrSize = 300 ## Maximum size of an encoded node record, in bytes.
|
||||
minRlpListLen = 4 ## Minimum node record RLP list has: signature, seqId,
|
||||
## "id" key and value.
|
||||
|
||||
type
|
||||
FieldPair* = (string, Field)
|
||||
|
||||
Record* = object
|
||||
seqNum*: uint64
|
||||
# signature: seq[byte]
|
||||
raw*: seq[byte] # RLP encoded record
|
||||
pairs: seq[FieldPair] # sorted list of all key/value pairs
|
||||
|
||||
EnrUri* = distinct string
|
||||
|
||||
TypedRecord* = object
|
||||
id*: string
|
||||
secp256k1*: Option[array[33, byte]]
|
||||
ip*: Option[array[4, byte]]
|
||||
ip6*: Option[array[16, byte]]
|
||||
tcp*: Option[int]
|
||||
udp*: Option[int]
|
||||
tcp6*: Option[int]
|
||||
udp6*: Option[int]
|
||||
|
||||
FieldKind = enum
|
||||
kString,
|
||||
kNum,
|
||||
kBytes,
|
||||
kList
|
||||
|
||||
Field = object
|
||||
case kind: FieldKind
|
||||
of kString:
|
||||
str: string
|
||||
of kNum:
|
||||
num: BiggestUInt
|
||||
of kBytes:
|
||||
bytes: seq[byte]
|
||||
of kList:
|
||||
listRaw: seq[byte] ## Differently from the other kinds, this is is stored
|
||||
## as raw (encoded) RLP data, and thus treated as such further on.
|
||||
|
||||
EnrResult*[T] = Result[T, cstring]
|
||||
|
||||
template toField[T](v: T): Field =
|
||||
when T is string:
|
||||
Field(kind: kString, str: v)
|
||||
elif T is array:
|
||||
Field(kind: kBytes, bytes: @v)
|
||||
elif T is seq[byte]:
|
||||
Field(kind: kBytes, bytes: v)
|
||||
elif T is SomeUnsignedInt:
|
||||
Field(kind: kNum, num: BiggestUInt(v))
|
||||
elif T is object|tuple:
|
||||
Field(kind: kList, listRaw: rlp.encode(v))
|
||||
else:
|
||||
{.error: "Unsupported field type".}
|
||||
|
||||
proc `==`(a, b: Field): bool =
|
||||
if a.kind == b.kind:
|
||||
case a.kind
|
||||
of kString:
|
||||
return a.str == b.str
|
||||
of kNum:
|
||||
return a.num == b.num
|
||||
of kBytes:
|
||||
return a.bytes == b.bytes
|
||||
of kList:
|
||||
return a.listRaw == b.listRaw
|
||||
else:
|
||||
return false
|
||||
|
||||
proc cmp(a, b: FieldPair): int = cmp(a[0], b[0])
|
||||
|
||||
proc makeEnrRaw(seqNum: uint64, pk: PrivateKey,
|
||||
pairs: openArray[FieldPair]): EnrResult[seq[byte]] =
|
||||
proc append(w: var RlpWriter, seqNum: uint64,
|
||||
pairs: openArray[FieldPair]): seq[byte] =
|
||||
w.append(seqNum)
|
||||
for (k, v) in pairs:
|
||||
w.append(k)
|
||||
case v.kind
|
||||
of kString: w.append(v.str)
|
||||
of kNum: w.append(v.num)
|
||||
of kBytes: w.append(v.bytes)
|
||||
of kList: w.appendRawBytes(v.listRaw) # No encoding needs to happen
|
||||
w.finish()
|
||||
|
||||
let toSign = block:
|
||||
var w = initRlpList(pairs.len * 2 + 1)
|
||||
w.append(seqNum, pairs)
|
||||
|
||||
let sig = signNR(pk, toSign)
|
||||
|
||||
var raw = block:
|
||||
var w = initRlpList(pairs.len * 2 + 2)
|
||||
w.append(sig.toRaw())
|
||||
w.append(seqNum, pairs)
|
||||
|
||||
if raw.len > maxEnrSize:
|
||||
err("Record exceeds maximum size")
|
||||
else:
|
||||
ok(raw)
|
||||
|
||||
proc makeEnrAux(seqNum: uint64, pk: PrivateKey,
|
||||
pairs: openArray[FieldPair]): EnrResult[Record] =
|
||||
var record: Record
|
||||
record.pairs = @pairs
|
||||
record.seqNum = seqNum
|
||||
|
||||
let pubkey = pk.toPublicKey()
|
||||
|
||||
record.pairs.add(("id", Field(kind: kString, str: "v4")))
|
||||
record.pairs.add(("secp256k1",
|
||||
Field(kind: kBytes, bytes: @(pubkey.toRawCompressed()))))
|
||||
|
||||
# Sort by key
|
||||
record.pairs.sort(cmp)
|
||||
# TODO: Should deduplicate on keys here also. Should we error on that or just
|
||||
# deal with it?
|
||||
|
||||
record.raw = ? makeEnrRaw(seqNum, pk, record.pairs)
|
||||
ok(record)
|
||||
|
||||
macro initRecord*(seqNum: uint64, pk: PrivateKey,
|
||||
pairs: untyped{nkTableConstr}): untyped =
|
||||
## Initialize a `Record` with given sequence number, private key and k:v
|
||||
## pairs.
|
||||
##
|
||||
## Can fail in case the record exceeds the `maxEnrSize`.
|
||||
for c in pairs:
|
||||
c.expectKind(nnkExprColonExpr)
|
||||
c[1] = newCall(bindSym"toField", c[1])
|
||||
|
||||
result = quote do:
|
||||
makeEnrAux(`seqNum`, `pk`, `pairs`)
|
||||
|
||||
template toFieldPair*(key: string, value: auto): FieldPair =
|
||||
(key, toField(value))
|
||||
|
||||
proc addAddress(fields: var seq[FieldPair], ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port]) =
|
||||
## Add address information in new fields. Incomplete address
|
||||
## information is allowed (example: Port but not IP) as that information
|
||||
## might be already in the ENR or added later.
|
||||
if ip.isSome():
|
||||
let
|
||||
ipExt = ip.get()
|
||||
isV6 = ipExt.family == IPv6
|
||||
|
||||
fields.add(if isV6: ("ip6", ipExt.address_v6.toField)
|
||||
else: ("ip", ipExt.address_v4.toField))
|
||||
if tcpPort.isSome():
|
||||
fields.add(((if isV6: "tcp6" else: "tcp"), tcpPort.get().uint16.toField))
|
||||
if udpPort.isSome():
|
||||
fields.add(((if isV6: "udp6" else: "udp"), udpPort.get().uint16.toField))
|
||||
else:
|
||||
if tcpPort.isSome():
|
||||
fields.add(("tcp", tcpPort.get().uint16.toField))
|
||||
if udpPort.isSome():
|
||||
fields.add(("udp", udpPort.get().uint16.toField))
|
||||
|
||||
proc init*(T: type Record, seqNum: uint64,
|
||||
pk: PrivateKey,
|
||||
ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port],
|
||||
extraFields: openArray[FieldPair] = []):
|
||||
EnrResult[T] =
|
||||
## Initialize a `Record` with given sequence number, private key, optional
|
||||
## ip address, tcp port, udp port, and optional custom k:v pairs.
|
||||
##
|
||||
## Can fail in case the record exceeds the `maxEnrSize`.
|
||||
var fields = newSeq[FieldPair]()
|
||||
|
||||
# TODO: Allow for initializing ENR with both ip4 and ipv6 address.
|
||||
fields.addAddress(ip, tcpPort, udpPort)
|
||||
fields.add extraFields
|
||||
makeEnrAux(seqNum, pk, fields)
|
||||
|
||||
proc getField(r: Record, name: string, field: var Field): bool =
|
||||
# It might be more correct to do binary search,
|
||||
# as the fields are sorted, but it's unlikely to
|
||||
# make any difference in reality.
|
||||
for (k, v) in r.pairs:
|
||||
if k == name:
|
||||
field = v
|
||||
return true
|
||||
|
||||
proc requireKind(f: Field, kind: FieldKind): EnrResult[void] =
|
||||
if f.kind != kind:
|
||||
err("Wrong field kind")
|
||||
else:
|
||||
ok()
|
||||
|
||||
proc get*(r: Record, key: string, T: type): EnrResult[T] =
|
||||
## Get the value from the provided key.
|
||||
var f: Field
|
||||
if r.getField(key, f):
|
||||
when T is SomeInteger:
|
||||
? requireKind(f, kNum)
|
||||
ok(T(f.num))
|
||||
elif T is seq[byte]:
|
||||
? requireKind(f, kBytes)
|
||||
ok(f.bytes)
|
||||
elif T is string:
|
||||
? requireKind(f, kString)
|
||||
ok(f.str)
|
||||
elif T is PublicKey:
|
||||
? requireKind(f, kBytes)
|
||||
let pk = PublicKey.fromRaw(f.bytes)
|
||||
if pk.isErr:
|
||||
err("Invalid public key")
|
||||
else:
|
||||
ok(pk[])
|
||||
elif T is array:
|
||||
when type(default(T)[low(T)]) is byte:
|
||||
? requireKind(f, kBytes)
|
||||
if f.bytes.len != T.len:
|
||||
err("Invalid byte blob length")
|
||||
else:
|
||||
var res: T
|
||||
copyMem(addr res[0], addr f.bytes[0], res.len)
|
||||
ok(res)
|
||||
else:
|
||||
{.fatal: "Unsupported output type in enr.get".}
|
||||
else:
|
||||
{.fatal: "Unsupported output type in enr.get".}
|
||||
else:
|
||||
err("Key not found in ENR")
|
||||
|
||||
proc get*(r: Record, T: type PublicKey): Option[T] =
|
||||
## Get the `PublicKey` from provided `Record`. Return `none` when there is
|
||||
## no `PublicKey` in the record.
|
||||
var pubkeyField: Field
|
||||
if r.getField("secp256k1", pubkeyField) and pubkeyField.kind == kBytes:
|
||||
let pk = PublicKey.fromRaw(pubkeyField.bytes)
|
||||
if pk.isOk:
|
||||
return some pk[]
|
||||
|
||||
proc find(r: Record, key: string): Option[int] =
|
||||
## Search for key in record key:value pairs.
|
||||
##
|
||||
## Returns some(index of key) if key is found in record. Else return none.
|
||||
for i, (k, v) in r.pairs:
|
||||
if k == key:
|
||||
return some(i)
|
||||
|
||||
proc update*(record: var Record, pk: PrivateKey,
|
||||
fieldPairs: openArray[FieldPair]): EnrResult[void] =
|
||||
## Update a `Record` k:v pairs.
|
||||
##
|
||||
## In case any of the k:v pairs is updated or added (new), the sequence number
|
||||
## of the `Record` will be incremented and a new signature will be applied.
|
||||
##
|
||||
## Can fail in case of wrong `PrivateKey`, if the size of the resulting record
|
||||
## exceeds `maxEnrSize` or if maximum sequence number is reached. The `Record`
|
||||
## will not be altered in these cases.
|
||||
var r = record
|
||||
|
||||
let pubkey = r.get(PublicKey)
|
||||
if pubkey.isNone() or pubkey.get() != pk.toPublicKey():
|
||||
return err("Public key does not correspond with given private key")
|
||||
|
||||
var updated = false
|
||||
for fieldPair in fieldPairs:
|
||||
let index = r.find(fieldPair[0])
|
||||
if(index.isSome()):
|
||||
if r.pairs[index.get()][1] == fieldPair[1]:
|
||||
# Exact k:v pair is already in record, nothing to do here.
|
||||
continue
|
||||
else:
|
||||
# Need to update the value.
|
||||
r.pairs[index.get()] = fieldPair
|
||||
updated = true
|
||||
else:
|
||||
# Add new k:v pair.
|
||||
r.pairs.insert(fieldPair, lowerBound(r.pairs, fieldPair, cmp))
|
||||
updated = true
|
||||
|
||||
if updated:
|
||||
if r.seqNum == high(r.seqNum): # highly unlikely
|
||||
return err("Maximum sequence number reached")
|
||||
r.seqNum.inc()
|
||||
r.raw = ? makeEnrRaw(r.seqNum, pk, r.pairs)
|
||||
record = r
|
||||
|
||||
ok()
|
||||
|
||||
proc update*(r: var Record, pk: PrivateKey,
|
||||
ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port] = none[Port](),
|
||||
extraFields: openArray[FieldPair] = []):
|
||||
EnrResult[void] =
|
||||
## Update a `Record` with given ip address, tcp port, udp port and optional
|
||||
## custom k:v pairs.
|
||||
##
|
||||
## In case any of the k:v pairs is updated or added (new), the sequence number
|
||||
## of the `Record` will be incremented and a new signature will be applied.
|
||||
##
|
||||
## Can fail in case of wrong `PrivateKey`, if the size of the resulting record
|
||||
## exceeds `maxEnrSize` or if maximum sequence number is reached. The `Record`
|
||||
## will not be altered in these cases.
|
||||
var fields = newSeq[FieldPair]()
|
||||
|
||||
# TODO: Make updating of both ipv4 and ipv6 address in ENR more convenient.
|
||||
fields.addAddress(ip, tcpPort, udpPort)
|
||||
fields.add extraFields
|
||||
r.update(pk, fields)
|
||||
|
||||
proc tryGet*(r: Record, key: string, T: type): Option[T] =
|
||||
## Get the value from the provided key.
|
||||
## Return `none` if the key does not exist or if the value is invalid
|
||||
## according to type `T`.
|
||||
let val = get(r, key, T)
|
||||
if val.isOk():
|
||||
some(val.get())
|
||||
else:
|
||||
none(T)
|
||||
|
||||
proc toTypedRecord*(r: Record): EnrResult[TypedRecord] =
|
||||
let id = r.tryGet("id", string)
|
||||
if id.isSome:
|
||||
var tr: TypedRecord
|
||||
tr.id = id.get
|
||||
|
||||
template readField(fieldName: untyped) {.dirty.} =
|
||||
tr.fieldName = tryGet(r, astToStr(fieldName), type(tr.fieldName.get))
|
||||
|
||||
readField secp256k1
|
||||
readField ip
|
||||
readField ip6
|
||||
readField tcp
|
||||
readField tcp6
|
||||
readField udp
|
||||
readField udp6
|
||||
|
||||
ok(tr)
|
||||
else:
|
||||
err("Record without id field")
|
||||
|
||||
proc contains*(r: Record, fp: (string, seq[byte])): bool =
|
||||
# TODO: use FieldPair for this, but that is a bit cumbersome. Perhaps the
|
||||
# `get` call can be improved to make this easier.
|
||||
let field = r.tryGet(fp[0], seq[byte])
|
||||
if field.isSome():
|
||||
if field.get() == fp[1]:
|
||||
return true
|
||||
|
||||
proc verifySignatureV4(r: Record, sigData: openArray[byte], content: seq[byte]):
|
||||
bool =
|
||||
let publicKey = r.get(PublicKey)
|
||||
if publicKey.isSome:
|
||||
let sig = SignatureNR.fromRaw(sigData)
|
||||
if sig.isOk:
|
||||
var h = keccak256.digest(content)
|
||||
return verify(sig[], SkMessage(h.data), publicKey.get)
|
||||
|
||||
proc verifySignature(r: Record): bool {.raises: [RlpError, Defect].} =
|
||||
var rlp = rlpFromBytes(r.raw)
|
||||
let sz = rlp.listLen
|
||||
if not rlp.enterList:
|
||||
return false
|
||||
let sigData = rlp.read(seq[byte])
|
||||
let content = block:
|
||||
var writer = initRlpList(sz - 1)
|
||||
var reader = rlp
|
||||
for i in 1 ..< sz:
|
||||
writer.appendRawBytes(reader.rawData)
|
||||
reader.skipElem
|
||||
writer.finish()
|
||||
|
||||
var id: Field
|
||||
if r.getField("id", id) and id.kind == kString:
|
||||
case id.str
|
||||
of "v4":
|
||||
result = verifySignatureV4(r, sigData, content)
|
||||
else:
|
||||
# Unknown Identity Scheme
|
||||
discard
|
||||
|
||||
proc fromBytesAux(r: var Record): bool {.raises: [RlpError, Defect].} =
|
||||
if r.raw.len > maxEnrSize:
|
||||
return false
|
||||
|
||||
var rlp = rlpFromBytes(r.raw)
|
||||
if not rlp.isList:
|
||||
return false
|
||||
|
||||
let sz = rlp.listLen
|
||||
if sz < minRlpListLen or sz mod 2 != 0:
|
||||
# Wrong rlp object
|
||||
return false
|
||||
|
||||
# We already know we are working with a list
|
||||
doAssert rlp.enterList()
|
||||
rlp.skipElem() # Skip signature
|
||||
|
||||
r.seqNum = rlp.read(uint64)
|
||||
|
||||
let numPairs = (sz - 2) div 2
|
||||
|
||||
for i in 0 ..< numPairs:
|
||||
let k = rlp.read(string)
|
||||
case k
|
||||
of "id":
|
||||
let id = rlp.read(string)
|
||||
r.pairs.add((k, Field(kind: kString, str: id)))
|
||||
of "secp256k1":
|
||||
let pubkeyData = rlp.read(seq[byte])
|
||||
r.pairs.add((k, Field(kind: kBytes, bytes: pubkeyData)))
|
||||
of "tcp", "udp", "tcp6", "udp6":
|
||||
let v = rlp.read(uint16)
|
||||
r.pairs.add((k, Field(kind: kNum, num: v)))
|
||||
else:
|
||||
# Don't know really what this is supposed to represent so drop it in
|
||||
# `kBytes` field pair when a single byte or blob.
|
||||
if rlp.isSingleByte() or rlp.isBlob():
|
||||
r.pairs.add((k, Field(kind: kBytes, bytes: rlp.read(seq[byte]))))
|
||||
elif rlp.isList():
|
||||
# Not supporting decoding lists as value (especially unknown ones),
|
||||
# just drop the raw RLP value in there.
|
||||
r.pairs.add((k, Field(kind: kList, listRaw: @(rlp.rawData()))))
|
||||
# Need to skip the element still.
|
||||
rlp.skipElem()
|
||||
|
||||
verifySignature(r)
|
||||
|
||||
proc fromBytes*(r: var Record, s: openArray[byte]): bool =
|
||||
## Loads ENR from rlp-encoded bytes, and validates the signature.
|
||||
r.raw = @s
|
||||
try:
|
||||
result = fromBytesAux(r)
|
||||
except RlpError:
|
||||
discard
|
||||
|
||||
proc fromBase64*(r: var Record, s: string): bool =
|
||||
## Loads ENR from base64-encoded rlp-encoded bytes, and validates the
|
||||
## signature.
|
||||
try:
|
||||
r.raw = Base64Url.decode(s)
|
||||
result = fromBytesAux(r)
|
||||
except RlpError, Base64Error:
|
||||
discard
|
||||
|
||||
proc fromURI*(r: var Record, s: string): bool =
|
||||
## Loads ENR from its text encoding: base64-encoded rlp-encoded bytes,
|
||||
## prefixed with "enr:". Validates the signature.
|
||||
const prefix = "enr:"
|
||||
if s.startsWith(prefix):
|
||||
result = r.fromBase64(s[prefix.len .. ^1])
|
||||
|
||||
template fromURI*(r: var Record, url: EnrUri): bool =
|
||||
fromURI(r, string(url))
|
||||
|
||||
proc toBase64*(r: Record): string =
|
||||
result = Base64Url.encode(r.raw)
|
||||
|
||||
proc toURI*(r: Record): string = "enr:" & r.toBase64
|
||||
|
||||
proc `$`(f: Field): string =
|
||||
case f.kind
|
||||
of kNum:
|
||||
$f.num
|
||||
of kBytes:
|
||||
"0x" & f.bytes.toHex
|
||||
of kString:
|
||||
"\"" & f.str & "\""
|
||||
of kList:
|
||||
"(Raw RLP list) " & "0x" & f.listRaw.toHex
|
||||
|
||||
proc `$`*(r: Record): string =
|
||||
result = "("
|
||||
result &= $r.seqNum
|
||||
for (k, v) in r.pairs:
|
||||
result &= ", "
|
||||
result &= k
|
||||
result &= ": "
|
||||
# For IP addresses we print something prettier than the default kinds
|
||||
# Note: Could disallow for invalid IPs in ENR also.
|
||||
if k == "ip":
|
||||
let ip = r.tryGet("ip", array[4, byte])
|
||||
if ip.isSome():
|
||||
result &= $ipv4(ip.get())
|
||||
else:
|
||||
result &= "(Invalid) " & $v
|
||||
elif k == "ip6":
|
||||
let ip = r.tryGet("ip6", array[16, byte])
|
||||
if ip.isSome():
|
||||
result &= $ipv6(ip.get())
|
||||
else:
|
||||
result &= "(Invalid) " & $v
|
||||
else:
|
||||
result &= $v
|
||||
result &= ')'
|
||||
|
||||
proc `==`*(a, b: Record): bool = a.raw == b.raw
|
||||
|
||||
proc read*(rlp: var Rlp, T: typedesc[Record]):
|
||||
T {.raises: [RlpError, ValueError, Defect].} =
|
||||
if not rlp.hasData() or not result.fromBytes(rlp.rawData):
|
||||
# TODO: This could also just be an invalid signature, would be cleaner to
|
||||
# split of RLP deserialisation errors from this.
|
||||
raise newException(ValueError, "Could not deserialize")
|
||||
rlp.skipElem()
|
||||
|
||||
proc append*(rlpWriter: var RlpWriter, value: Record) =
|
||||
rlpWriter.appendRawBytes(value.raw)
|
@ -15,7 +15,7 @@
|
||||
import
|
||||
std/[hashes, net],
|
||||
eth/[keys],
|
||||
./enr,
|
||||
./spr,
|
||||
../../../../dht/providers_messages
|
||||
|
||||
export providers_messages
|
||||
@ -45,10 +45,10 @@ type
|
||||
id*: seq[byte]
|
||||
|
||||
PingMessage* = object
|
||||
enrSeq*: uint64
|
||||
sprSeq*: uint64
|
||||
|
||||
PongMessage* = object
|
||||
enrSeq*: uint64
|
||||
sprSeq*: uint64
|
||||
ip*: IpAddress
|
||||
port*: uint16
|
||||
|
||||
@ -57,7 +57,7 @@ type
|
||||
|
||||
NodesMessage* = object
|
||||
total*: uint32
|
||||
enrs*: seq[Record]
|
||||
sprs*: seq[SignedPeerRecord]
|
||||
|
||||
TalkReqMessage* = object
|
||||
protocol*: seq[byte]
|
||||
|
@ -15,7 +15,7 @@ import
|
||||
chronicles,
|
||||
libp2p/routing_record,
|
||||
libp2p/signed_envelope,
|
||||
"."/[messages, enr],
|
||||
"."/[messages, spr],
|
||||
../../../../dht/providers_encoding
|
||||
|
||||
from stew/objects import checkedEnumAssign
|
||||
|
@ -11,7 +11,7 @@ import
|
||||
std/hashes,
|
||||
nimcrypto, stint, chronos, stew/shims/net, chronicles,
|
||||
eth/keys, eth/net/utils,
|
||||
./enr
|
||||
./spr
|
||||
|
||||
export stint
|
||||
|
||||
@ -24,27 +24,27 @@ type
|
||||
|
||||
Node* = ref object
|
||||
id*: NodeId
|
||||
pubkey*: PublicKey
|
||||
pubkey*: keys.PublicKey
|
||||
address*: Option[Address]
|
||||
record*: Record
|
||||
record*: SignedPeerRecord
|
||||
seen*: bool ## Indicates if there was at least one successful
|
||||
## request-response with this node.
|
||||
|
||||
func toNodeId*(pk: PublicKey): NodeId =
|
||||
func toNodeId*(pk: keys.PublicKey): NodeId =
|
||||
## Convert public key to a node identifier.
|
||||
# Keccak256 hash is used as defined in ENR spec for scheme v4:
|
||||
# Keccak256 hash is used as defined in SPR spec for scheme v4:
|
||||
# https://github.com/ethereum/devp2p/blob/master/enr.md#v4-identity-scheme
|
||||
readUintBE[256](keccak256.digest(pk.toRaw()).data)
|
||||
|
||||
func newNode*(r: Record): Result[Node, cstring] =
|
||||
## Create a new `Node` from a `Record`.
|
||||
func newNode*(r: SignedPeerRecord): Result[Node, cstring] =
|
||||
## Create a new `Node` from a `SignedPeerRecord`.
|
||||
# TODO: Handle IPv6
|
||||
|
||||
let pk = r.get(PublicKey)
|
||||
let pk = r.get(keys.PublicKey)
|
||||
# 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.
|
||||
if pk.isNone():
|
||||
return err("Could not recover public key from ENR")
|
||||
return err("Could not recover public key from SPR")
|
||||
|
||||
# Also this can not fail for a properly created record as id is checked upon
|
||||
# deserialization.
|
||||
@ -58,10 +58,9 @@ func newNode*(r: Record): Result[Node, cstring] =
|
||||
ok(Node(id: pk.get().toNodeId(), pubkey: pk.get(), record: r,
|
||||
address: none(Address)))
|
||||
|
||||
func update*(n: Node, pk: PrivateKey, ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port] = none[Port](),
|
||||
extraFields: openArray[FieldPair] = []): Result[void, cstring] =
|
||||
? n.record.update(pk, ip, tcpPort, udpPort, extraFields)
|
||||
proc update*(n: Node, pk: keys.PrivateKey, ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port] = none[Port]()): Result[void, cstring] =
|
||||
? n.record.update(pk, ip, tcpPort, udpPort)
|
||||
|
||||
if ip.isSome():
|
||||
if udpPort.isSome():
|
||||
|
@ -3,7 +3,7 @@
|
||||
import
|
||||
std/[sets, options],
|
||||
stew/results, stew/shims/net, chronicles, chronos,
|
||||
"."/[node, enr, routing_table]
|
||||
"."/[node, spr, routing_table]
|
||||
|
||||
logScope:
|
||||
topics = "nodes-verification"
|
||||
@ -25,24 +25,24 @@ proc validIp(sender, address: IpAddress): bool =
|
||||
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
||||
return true
|
||||
|
||||
proc verifyNodesRecords(enrs: openArray[Record], fromNode: Node, nodesLimit: int,
|
||||
proc verifyNodesRecords(sprs: openArray[SignedPeerRecord], fromNode: Node, nodesLimit: int,
|
||||
distances: Option[seq[uint16]]): seq[Node] =
|
||||
## Verify and convert ENRs to a sequence of nodes. Only ENRs that pass
|
||||
## verification will be added. ENRs are verified for duplicates, invalid
|
||||
## Verify and convert SPRs to a sequence of nodes. Only SPRs that pass
|
||||
## verification will be added. SPRs are verified for duplicates, invalid
|
||||
## addresses and invalid distances if those are specified.
|
||||
var seen: HashSet[Node]
|
||||
var count = 0
|
||||
for r in enrs:
|
||||
# Check and allow for processing of maximum `findNodeResultLimit` ENRs
|
||||
# returned. This limitation is required so no huge lists of invalid ENRs
|
||||
for r in sprs:
|
||||
# Check and allow for processing of maximum `findNodeResultLimit` SPRs
|
||||
# returned. This limitation is required so no huge lists of invalid SPRs
|
||||
# are processed for no reason, and for not overwhelming a routing table
|
||||
# with nodes from a malicious actor.
|
||||
# The discovery v5 specification specifies no limit on the amount of ENRs
|
||||
# The discovery v5 specification specifies no limit on the amount of SPRs
|
||||
# that can be returned, but clients usually stick with the bucket size limit
|
||||
# as in original Kademlia. Because of this it is chosen not to fail
|
||||
# immediatly, but still process maximum `findNodeResultLimit`.
|
||||
if count >= nodesLimit:
|
||||
debug "Too many ENRs", enrs = enrs.len(),
|
||||
debug "Too many SPRs", sprs = sprs.len(),
|
||||
limit = nodesLimit, sender = fromNode.record.toURI
|
||||
break
|
||||
|
||||
@ -79,8 +79,8 @@ proc verifyNodesRecords(enrs: openArray[Record], fromNode: Node, nodesLimit: int
|
||||
seen.incl(n)
|
||||
result.add(n)
|
||||
|
||||
proc verifyNodesRecords*(enrs: openArray[Record], fromNode: Node, nodesLimit: int): seq[Node] =
|
||||
verifyNodesRecords(enrs, fromNode, nodesLimit, none[seq[uint16]]())
|
||||
proc verifyNodesRecords*(sprs: openArray[SignedPeerRecord], fromNode: Node, nodesLimit: int): seq[Node] =
|
||||
verifyNodesRecords(sprs, fromNode, nodesLimit, none[seq[uint16]]())
|
||||
|
||||
proc verifyNodesRecords*(enrs: openArray[Record], fromNode: Node, nodesLimit: int, distances: seq[uint16]): seq[Node] =
|
||||
verifyNodesRecords(enrs, fromNode, nodesLimit, some[seq[uint16]](distances))
|
||||
proc verifyNodesRecords*(sprs: openArray[SignedPeerRecord], fromNode: Node, nodesLimit: int, distances: seq[uint16]): seq[Node] =
|
||||
verifyNodesRecords(sprs, fromNode, nodesLimit, some[seq[uint16]](distances))
|
||||
|
@ -76,13 +76,13 @@
|
||||
import
|
||||
std/[tables, sets, options, math, sequtils, algorithm],
|
||||
stew/shims/net as stewNet, json_serialization/std/net,
|
||||
stew/[endians2, results], chronicles, chronos, chronos/timer, stint, bearssl,
|
||||
stew/[base64, endians2, results], chronicles, chronos, chronos/timer, stint, bearssl,
|
||||
metrics, eth/[rlp, keys, async_utils], libp2p/routing_record,
|
||||
"."/[transport, messages, messages_encoding, node, routing_table, enr, random2, ip_vote, nodes_verification]
|
||||
"."/[transport, messages, messages_encoding, node, routing_table, spr, random2, ip_vote, nodes_verification]
|
||||
|
||||
import nimcrypto except toHex
|
||||
|
||||
export options, results, node, enr
|
||||
export options, results, node, spr
|
||||
|
||||
declareCounter discovery_message_requests_outgoing,
|
||||
"Discovery protocol outgoing message requests", labels = ["response"]
|
||||
@ -91,7 +91,7 @@ declareCounter discovery_message_requests_incoming,
|
||||
declareCounter discovery_unsolicited_messages,
|
||||
"Discovery protocol unsolicited or timed-out messages"
|
||||
declareCounter discovery_enr_auto_update,
|
||||
"Amount of discovery IP:port address ENR auto updates"
|
||||
"Amount of discovery IP:port address SPR auto updates"
|
||||
|
||||
logScope:
|
||||
topics = "discv5"
|
||||
@ -100,15 +100,15 @@ const
|
||||
alpha = 3 ## Kademlia concurrency factor
|
||||
lookupRequestLimit = 3 ## Amount of distances requested in a single Findnode
|
||||
## message for a lookup or query
|
||||
findNodeResultLimit = 16 ## Maximum amount of ENRs in the total Nodes messages
|
||||
findNodeResultLimit = 16 ## Maximum amount of SPRs in the total Nodes messages
|
||||
## that will be processed
|
||||
maxNodesPerMessage = 3 ## Maximum amount of ENRs per individual Nodes message
|
||||
maxNodesPerMessage = 3 ## Maximum amount of SPRs per individual Nodes message
|
||||
refreshInterval = 5.minutes ## Interval of launching a random query to
|
||||
## refresh the routing table.
|
||||
revalidateMax = 10000 ## Revalidation of a peer is done between 0 and this
|
||||
## value in milliseconds
|
||||
ipMajorityInterval = 5.minutes ## Interval for checking the latest IP:Port
|
||||
## majority and updating this when ENR auto update is set.
|
||||
## majority and updating this when SPR auto update is set.
|
||||
initialLookups = 1 ## Amount of lookups done when populating the routing table
|
||||
responseTimeout* = 4.seconds ## timeout for the response of a request-response
|
||||
## call
|
||||
@ -128,7 +128,7 @@ type
|
||||
revalidateLoop: Future[void]
|
||||
ipMajorityLoop: Future[void]
|
||||
lastLookup: chronos.Moment
|
||||
bootstrapRecords*: seq[Record]
|
||||
bootstrapRecords*: seq[SignedPeerRecord]
|
||||
ipVote: IpVote
|
||||
enrAutoUpdate: bool
|
||||
talkProtocols*: Table[seq[byte], TalkProtocol] # TODO: Table is a bit of
|
||||
@ -159,24 +159,28 @@ proc addNode*(d: Protocol, node: Node): bool =
|
||||
else:
|
||||
return false
|
||||
|
||||
proc addNode*(d: Protocol, r: Record): bool =
|
||||
## Add `Node` from a `Record` to discovery routing table.
|
||||
proc addNode*(d: Protocol, r: SignedPeerRecord): bool =
|
||||
## Add `Node` from a `SignedPeerRecord` to discovery routing table.
|
||||
##
|
||||
## Returns false only if no valid `Node` can be created from the `Record` or
|
||||
## Returns false only if no valid `Node` can be created from the `SignedPeerRecord` or
|
||||
## on the conditions of `addNode` from a `Node`.
|
||||
let node = newNode(r)
|
||||
if node.isOk():
|
||||
return d.addNode(node[])
|
||||
|
||||
proc addNode*(d: Protocol, enr: EnrUri): bool =
|
||||
## Add `Node` from a ENR URI to discovery routing table.
|
||||
proc addNode*(d: Protocol, spr: SprUri): bool =
|
||||
## Add `Node` from a SPR URI to discovery routing table.
|
||||
##
|
||||
## Returns false if no valid ENR URI, or on the conditions of `addNode` from
|
||||
## an `Record`.
|
||||
var r: Record
|
||||
let res = r.fromURI(enr)
|
||||
if res:
|
||||
return d.addNode(r)
|
||||
## Returns false if no valid SPR URI, or on the conditions of `addNode` from
|
||||
## an `SignedPeerRecord`.
|
||||
try:
|
||||
var r: SignedPeerRecord
|
||||
let res = r.fromURI(spr)
|
||||
if res:
|
||||
return d.addNode(r)
|
||||
except Base64Error as e:
|
||||
error "Base64 error decoding SPR URI", error = e.msg
|
||||
return false
|
||||
|
||||
proc getNode*(d: Protocol, id: NodeId): Option[Node] =
|
||||
## Get the node with id from the routing table.
|
||||
@ -213,15 +217,17 @@ proc nodesDiscovered*(d: Protocol): int = d.routingTable.len
|
||||
func privKey*(d: Protocol): lent keys.PrivateKey =
|
||||
d.privateKey
|
||||
|
||||
func getRecord*(d: Protocol): Record =
|
||||
## Get the ENR of the local node.
|
||||
func getRecord*(d: Protocol): SignedPeerRecord =
|
||||
## Get the SPR of the local node.
|
||||
d.localNode.record
|
||||
|
||||
proc updateRecord*(
|
||||
d: Protocol, enrFields: openArray[(string, seq[byte])]): DiscResult[void] =
|
||||
proc updateRecord*(d: Protocol): DiscResult[void] =
|
||||
## Update the ENR of the local node with provided `enrFields` k:v pairs.
|
||||
let fields = mapIt(enrFields, toFieldPair(it[0], it[1]))
|
||||
d.localNode.record.update(d.privateKey, fields)
|
||||
|
||||
# TODO: Do we need this proc? This simply serves so that seqNo will be
|
||||
# incremented to satisfy the tests...
|
||||
d.localNode.record.incSeqNo(d.privateKey)
|
||||
|
||||
# TODO: Would it make sense to actively ping ("broadcast") to all the peers
|
||||
# we stored a handshake with in order to get that ENR updated?
|
||||
|
||||
@ -240,27 +246,27 @@ proc sendNodes(d: Protocol, toId: NodeId, toAddr: Address, reqId: RequestId,
|
||||
|
||||
if nodes.len == 0:
|
||||
# In case of 0 nodes, a reply is still needed
|
||||
d.sendNodes(toId, toAddr, NodesMessage(total: 1, enrs: @[]), reqId)
|
||||
d.sendNodes(toId, toAddr, NodesMessage(total: 1, sprs: @[]), reqId)
|
||||
return
|
||||
|
||||
var message: NodesMessage
|
||||
# TODO: Do the total calculation based on the max UDP packet size we want to
|
||||
# send and the ENR size of all (max 16) nodes.
|
||||
# send and the SPR size of all (max 16) nodes.
|
||||
# Which UDP packet size to take? 1280? 576?
|
||||
message.total = ceil(nodes.len / maxNodesPerMessage).uint32
|
||||
|
||||
for i in 0 ..< nodes.len:
|
||||
message.enrs.add(nodes[i].record)
|
||||
if message.enrs.len == maxNodesPerMessage:
|
||||
message.sprs.add(nodes[i].record)
|
||||
if message.sprs.len == maxNodesPerMessage:
|
||||
d.sendNodes(toId, toAddr, message, reqId)
|
||||
message.enrs.setLen(0)
|
||||
message.sprs.setLen(0)
|
||||
|
||||
if message.enrs.len != 0:
|
||||
if message.sprs.len != 0:
|
||||
d.sendNodes(toId, toAddr, message, reqId)
|
||||
|
||||
proc handlePing(d: Protocol, fromId: NodeId, fromAddr: Address,
|
||||
ping: PingMessage, reqId: RequestId) =
|
||||
let pong = PongMessage(enrSeq: d.localNode.record.seqNum, ip: fromAddr.ip,
|
||||
let pong = PongMessage(sprSeq: d.localNode.record.seqNum, ip: fromAddr.ip,
|
||||
port: fromAddr.port.uint16)
|
||||
trace "Respond message packet", dstId = fromId, address = fromAddr,
|
||||
kind = MessageKind.pong
|
||||
@ -369,7 +375,7 @@ proc replaceNode(d: Protocol, n: Node) =
|
||||
# For now we never remove bootstrap nodes. It might make sense to actually
|
||||
# do so and to retry them only in case we drop to a really low amount of
|
||||
# peers in the routing table.
|
||||
debug "Message request to bootstrap node failed", enr = toURI(n.record)
|
||||
debug "Message request to bootstrap node failed", spr = toURI(n.record)
|
||||
|
||||
|
||||
proc waitMessage(d: Protocol, fromNode: Node, reqId: RequestId):
|
||||
@ -384,7 +390,7 @@ proc waitMessage(d: Protocol, fromNode: Node, reqId: RequestId):
|
||||
d.awaitedMessages[key] = result
|
||||
|
||||
proc waitNodes(d: Protocol, fromNode: Node, reqId: RequestId):
|
||||
Future[DiscResult[seq[Record]]] {.async.} =
|
||||
Future[DiscResult[seq[SignedPeerRecord]]] {.async.} =
|
||||
## Wait for one or more nodes replies.
|
||||
##
|
||||
## The first reply will hold the total number of replies expected, and based
|
||||
@ -394,12 +400,12 @@ proc waitNodes(d: Protocol, fromNode: Node, reqId: RequestId):
|
||||
var op = await d.waitMessage(fromNode, reqId)
|
||||
if op.isSome:
|
||||
if op.get.kind == nodes:
|
||||
var res = op.get.nodes.enrs
|
||||
var res = op.get.nodes.sprs
|
||||
let total = op.get.nodes.total
|
||||
for i in 1 ..< total:
|
||||
op = await d.waitMessage(fromNode, reqId)
|
||||
if op.isSome and op.get.kind == nodes:
|
||||
res.add(op.get.nodes.enrs)
|
||||
res.add(op.get.nodes.sprs)
|
||||
else:
|
||||
# No error on this as we received some nodes.
|
||||
break
|
||||
@ -443,7 +449,7 @@ proc ping*(d: Protocol, toNode: Node):
|
||||
##
|
||||
## Returns the received pong message or an error.
|
||||
let reqId = d.sendRequest(toNode,
|
||||
PingMessage(enrSeq: d.localNode.record.seqNum))
|
||||
PingMessage(sprSeq: d.localNode.record.seqNum))
|
||||
let resp = await d.waitMessage(toNode, reqId)
|
||||
|
||||
if resp.isSome():
|
||||
@ -464,7 +470,7 @@ proc findNode*(d: Protocol, toNode: Node, distances: seq[uint16]):
|
||||
## Send a discovery findNode message.
|
||||
##
|
||||
## Returns the received nodes or an error.
|
||||
## Received ENRs are already validated and converted to `Node`.
|
||||
## Received SPRs are already validated and converted to `Node`.
|
||||
let reqId = d.sendRequest(toNode, FindNodeMessage(distances: distances))
|
||||
let nodes = await d.waitNodes(toNode, reqId)
|
||||
|
||||
@ -799,8 +805,8 @@ proc revalidateNode*(d: Protocol, n: Node) {.async.} =
|
||||
|
||||
if pong.isOk():
|
||||
let res = pong.get()
|
||||
if res.enrSeq > n.record.seqNum:
|
||||
# Request new ENR
|
||||
if res.sprSeq > n.record.seqNum:
|
||||
# Request new SPR
|
||||
let nodes = await d.findNode(n, @[0'u16])
|
||||
if nodes.isOk() and nodes[].len > 0:
|
||||
discard d.addNode(nodes[][0])
|
||||
@ -841,7 +847,7 @@ proc refreshLoop(d: Protocol) {.async.} =
|
||||
|
||||
proc ipMajorityLoop(d: Protocol) {.async.} =
|
||||
## When `enrAutoUpdate` is enabled, the IP:port combination returned
|
||||
## by the majority will be used to update the local ENR.
|
||||
## by the majority will be used to update the local SPR.
|
||||
## This should be safe as long as the routing table is not overwhelmed by
|
||||
## malicious nodes trying to provide invalid addresses.
|
||||
## Why is that?
|
||||
@ -869,14 +875,14 @@ proc ipMajorityLoop(d: Protocol) {.async.} =
|
||||
let res = d.localNode.update(d.privateKey,
|
||||
ip = some(address.ip), udpPort = some(address.port))
|
||||
if res.isErr:
|
||||
warn "Failed updating ENR with newly discovered external address",
|
||||
warn "Failed updating SPR with newly discovered external address",
|
||||
majority, previous, error = res.error
|
||||
else:
|
||||
discovery_enr_auto_update.inc()
|
||||
info "Updated ENR with newly discovered external address",
|
||||
info "Updated SPR with newly discovered external address",
|
||||
majority, previous, uri = toURI(d.localNode.record)
|
||||
else:
|
||||
warn "Discovered new external address but ENR auto update is off",
|
||||
warn "Discovered new external address but SPR auto update is off",
|
||||
majority, previous
|
||||
else:
|
||||
debug "Discovered external address matches current address", majority,
|
||||
@ -904,8 +910,8 @@ proc newProtocol*(
|
||||
enrIp: Option[ValidIpAddress],
|
||||
enrTcpPort, enrUdpPort: Option[Port],
|
||||
localEnrFields: openArray[(string, seq[byte])] = [],
|
||||
bootstrapRecords: openArray[Record] = [],
|
||||
previousRecord = none[enr.Record](),
|
||||
bootstrapRecords: openArray[SignedPeerRecord] = [],
|
||||
previousRecord = none[SignedPeerRecord](),
|
||||
bindPort: Port,
|
||||
bindIp = IPv4_any(),
|
||||
enrAutoUpdate = false,
|
||||
@ -917,27 +923,30 @@ proc newProtocol*(
|
||||
# Anyhow, nim-beacon-chain would also require some changes to support port
|
||||
# remapping through NAT and this API is also subject to change once we
|
||||
# introduce support for ipv4 + ipv6 binding/listening.
|
||||
let extraFields = mapIt(localEnrFields, toFieldPair(it[0], it[1]))
|
||||
|
||||
# TODO: Implement SignedPeerRecord custom fields?
|
||||
# let extraFields = mapIt(localEnrFields, toFieldPair(it[0], it[1]))
|
||||
|
||||
# TODO:
|
||||
# - Defect as is now or return a result for enr errors?
|
||||
# - In case incorrect key, allow for new enr based on new key (new node id)?
|
||||
var record: Record
|
||||
# - Defect as is now or return a result for spr errors?
|
||||
# - In case incorrect key, allow for new spr based on new key (new node id)?
|
||||
var record: SignedPeerRecord
|
||||
if previousRecord.isSome():
|
||||
record = previousRecord.get()
|
||||
record.update(privKey, enrIp, enrTcpPort, enrUdpPort,
|
||||
extraFields).expect("Record within size limits and correct key")
|
||||
record.update(privKey, enrIp, enrTcpPort, enrUdpPort)
|
||||
.expect("SignedPeerRecord within size limits and correct key")
|
||||
else:
|
||||
record = enr.Record.init(1, privKey, enrIp, enrTcpPort, enrUdpPort,
|
||||
extraFields).expect("Record within size limits")
|
||||
record = SignedPeerRecord.init(1, privKey, enrIp, enrTcpPort, enrUdpPort)
|
||||
.expect("SignedPeerRecord within size limits")
|
||||
|
||||
info "ENR initialized", ip = enrIp, tcp = enrTcpPort, udp = enrUdpPort,
|
||||
info "SPR initialized", ip = enrIp, tcp = enrTcpPort, udp = enrUdpPort,
|
||||
seqNum = record.seqNum, uri = toURI(record)
|
||||
if enrIp.isNone():
|
||||
if enrAutoUpdate:
|
||||
notice "No external IP provided for the ENR, this node will not be " &
|
||||
"discoverable until the ENR is updated with the discovered external IP address"
|
||||
notice "No external IP provided for the SPR, this node will not be " &
|
||||
"discoverable until the SPR is updated with the discovered external IP address"
|
||||
else:
|
||||
warn "No external IP provided for the ENR, this node will not be discoverable"
|
||||
warn "No external IP provided for the SPR, this node will not be discoverable"
|
||||
|
||||
let node = newNode(record).expect("Properly initialized record")
|
||||
|
||||
|
@ -11,7 +11,7 @@ import
|
||||
std/[algorithm, times, sequtils, bitops, sets, options],
|
||||
stint, chronicles, metrics, bearssl, chronos, stew/shims/net as stewNet,
|
||||
eth/net/utils,
|
||||
"."/[node, random2, enr]
|
||||
"."/[node, random2, spr]
|
||||
|
||||
export options
|
||||
|
||||
@ -67,9 +67,9 @@ type
|
||||
##
|
||||
## As entries are not verified (=contacted) immediately before or on entry, it
|
||||
## is possible that a malicious node could fill (poison) the routing table or
|
||||
## a specific bucket with ENRs with IPs it does not control. The effect of
|
||||
## a specific bucket with SPRs with IPs it does not control. The effect of
|
||||
## this would be that a node that actually owns the IP could have a difficult
|
||||
## time getting its ENR distrubuted in the DHT and as a consequence would
|
||||
## time getting its SPR distrubuted in the DHT and as a consequence would
|
||||
## not be reached from the outside as much (or at all). However, that node can
|
||||
## still search and find nodes to connect to. So it would practically be a
|
||||
## similar situation as a node that is not reachable behind the NAT because
|
||||
@ -102,7 +102,7 @@ func logDistance*(a, b: NodeId): uint16 =
|
||||
##
|
||||
## According the specification, this is the log base 2 of the distance. But it
|
||||
## is rather the log base 2 of the distance + 1, as else the 0 value can not
|
||||
## be used (e.g. by FindNode call to return peer its own ENR)
|
||||
## be used (e.g. by FindNode call to return peer its own SPR)
|
||||
## For NodeId of 256 bits, range is 0-256.
|
||||
let a = a.toBytesBE
|
||||
let b = b.toBytesBE
|
||||
@ -330,7 +330,7 @@ proc addNode*(r: var RoutingTable, n: Node): NodeStatus =
|
||||
## total routing table, the node will not be added to the bucket, nor its
|
||||
## replacement cache.
|
||||
|
||||
# Don't allow nodes without an address field in the ENR to be added.
|
||||
# Don't allow nodes without an address field in the SPR to be added.
|
||||
# This could also be reworked by having another Node type that always has an
|
||||
# address.
|
||||
if n.address.isNone():
|
||||
@ -351,7 +351,7 @@ proc addNode*(r: var RoutingTable, n: Node): NodeStatus =
|
||||
if not ipLimitInc(r, bucket, n):
|
||||
return IpLimitReached
|
||||
ipLimitDec(r, bucket, bucket.nodes[nodeIdx])
|
||||
# Copy over the seen status, we trust here that after the ENR update the
|
||||
# Copy over the seen status, we trust here that after the SPR update the
|
||||
# node will still be reachable, but it might not be the case.
|
||||
n.seen = bucket.nodes[nodeIdx].seen
|
||||
bucket.nodes[nodeIdx] = n
|
||||
@ -368,7 +368,7 @@ proc addNode*(r: var RoutingTable, n: Node): NodeStatus =
|
||||
# newly additions are added as least recently seen (in fact they have not been
|
||||
# seen yet from our node its perspective).
|
||||
# However, in discovery v5 a node can also be added after a incoming request
|
||||
# if a handshake is done and an ENR is provided, and considering that this
|
||||
# if a handshake is done and an SPR is provided, and considering that this
|
||||
# handshake needs to be done, it is more likely that this node is reachable.
|
||||
# However, it is not certain and depending on different NAT mechanisms and
|
||||
# timers it might still fail. For this reason we currently do not add a way to
|
||||
|
360
libp2pdht/private/eth/p2p/discoveryv5/spr.nim
Normal file
360
libp2pdht/private/eth/p2p/discoveryv5/spr.nim
Normal file
@ -0,0 +1,360 @@
|
||||
# Copyright (c) 2020-2022 Status Research & Development GmbH
|
||||
# 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.
|
||||
#
|
||||
import
|
||||
chronicles,
|
||||
std/[options, sequtils, strutils, sugar],
|
||||
pkg/stew/[results, byteutils, arrayops],
|
||||
stew/endians2,
|
||||
stew/shims/net,
|
||||
stew/base64,
|
||||
eth/rlp,
|
||||
eth/keys,
|
||||
libp2p/crypto/crypto,
|
||||
libp2p/crypto/secp,
|
||||
libp2p/routing_record,
|
||||
libp2p/multicodec
|
||||
|
||||
export routing_record
|
||||
|
||||
from chronos import TransportAddress, initTAddress
|
||||
|
||||
export options, results
|
||||
|
||||
type
|
||||
SprUri* = distinct string
|
||||
|
||||
RecordResult*[T] = Result[T, cstring]
|
||||
|
||||
proc seqNum*(r: SignedPeerRecord): uint64 =
|
||||
r.data.seqNo
|
||||
|
||||
#proc encode
|
||||
proc append*(rlpWriter: var RlpWriter, value: SignedPeerRecord) =
|
||||
# echo "encoding to:" & $value.signedPeerRecord.encode.get
|
||||
var encoded = value.encode
|
||||
trace "Encoding SignedPeerRecord for RLP", bytes = encoded.get(@[])
|
||||
if encoded.isErr:
|
||||
error "Error encoding SignedPeerRecord for RLP", error = encoded.error
|
||||
rlpWriter.append encoded.get(@[])
|
||||
|
||||
proc fromBytes(r: var SignedPeerRecord, s: openArray[byte]): bool =
|
||||
trace "Decoding SignedPeerRecord for RLP", bytes = s
|
||||
|
||||
let decoded = SignedPeerRecord.decode(@s)
|
||||
if decoded.isErr:
|
||||
error "Error decoding SignedPeerRecord", error = decoded.error
|
||||
return false
|
||||
|
||||
r = decoded.get
|
||||
return true
|
||||
|
||||
proc read*(rlp: var Rlp, T: typedesc[SignedPeerRecord]):
|
||||
T {.raises: [RlpError, ValueError, Defect].} =
|
||||
# echo "read:" & $rlp.rawData
|
||||
## code directly borrowed from spr.nim
|
||||
trace "Reading RLP SignedPeerRecord", rawData = rlp.rawData, toBytes = rlp.toBytes
|
||||
if not rlp.hasData() or not result.fromBytes(rlp.toBytes):
|
||||
# TODO: This could also just be an invalid signature, would be cleaner to
|
||||
# split of RLP deserialisation errors from this.
|
||||
raise newException(ValueError, "Could not deserialize")
|
||||
rlp.skipElem()
|
||||
|
||||
proc get*(r: SignedPeerRecord, T: type crypto.PublicKey): Option[T] =
|
||||
## Get the `PublicKey` from provided `Record`. Return `none` when there is
|
||||
## no `PublicKey` in the record.
|
||||
some(r.envelope.publicKey)
|
||||
|
||||
func pkToPk(pk: crypto.PublicKey) : Option[keys.PublicKey] =
|
||||
some((keys.PublicKey)(pk.skkey))
|
||||
|
||||
func pkToPk(pk: keys.PublicKey) : Option[crypto.PublicKey] =
|
||||
some(crypto.PublicKey.init((secp.SkPublicKey)(pk)))
|
||||
|
||||
func pkToPk(pk: crypto.PrivateKey) : Option[keys.PrivateKey] =
|
||||
some((keys.PrivateKey)(pk.skkey))
|
||||
|
||||
func pkToPk(pk: keys.PrivateKey) : Option[crypto.PrivateKey] =
|
||||
some(crypto.PrivateKey.init((secp.SkPrivateKey)(pk)))
|
||||
|
||||
proc get*(r: SignedPeerRecord, T: type keys.PublicKey): Option[T] =
|
||||
## Get the `PublicKey` from provided `Record`. Return `none` when there is
|
||||
## no `PublicKey` in the record.
|
||||
## PublicKey* = distinct SkPublicKey
|
||||
let
|
||||
pk = r.envelope.publicKey
|
||||
pkToPk(pk)
|
||||
|
||||
proc incSeqNo*(
|
||||
r: var SignedPeerRecord,
|
||||
pk: keys.PrivateKey): RecordResult[void] =
|
||||
|
||||
let cryptoPk = pk.pkToPk.get() # TODO: remove when eth/keys removed
|
||||
|
||||
r.data.seqNo.inc()
|
||||
r = ? SignedPeerRecord.init(cryptoPk, r.data).mapErr(
|
||||
(e: CryptoError) =>
|
||||
("Error initialising SignedPeerRecord with incremented seqNo: " &
|
||||
$e).cstring
|
||||
)
|
||||
ok()
|
||||
|
||||
|
||||
proc update*(r: var SignedPeerRecord, pk: crypto.PrivateKey,
|
||||
ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port] = none[Port]()):
|
||||
RecordResult[void] =
|
||||
## Update a `SignedPeerRecord` with given ip address, tcp port, udp port and optional
|
||||
## custom k:v pairs.
|
||||
##
|
||||
## In case any of the k:v pairs is updated or added (new), the sequence number
|
||||
## of the `Record` will be incremented and a new signature will be applied.
|
||||
##
|
||||
## Can fail in case of wrong `PrivateKey`, if the size of the resulting record
|
||||
## exceeds `maxSprSize` or if maximum sequence number is reached. The `Record`
|
||||
## will not be altered in these cases.
|
||||
|
||||
# TODO: handle custom field pairs?
|
||||
# TODO: We have a mapping issue here because PeerRecord has multiple
|
||||
# addresses and the proc signature only allows updating of a single
|
||||
# ip/tcpPort/udpPort/extraFields
|
||||
|
||||
let
|
||||
pubkey = r.get(crypto.PublicKey)
|
||||
keysPubKey = pubkey.get.pkToPk.get # remove when move away from eth/keys
|
||||
keysPrivKey = pk.pkToPk.get
|
||||
if pubkey.isNone() or keysPubKey != keysPrivKey.toPublicKey:
|
||||
return err("Public key does not correspond with given private key")
|
||||
|
||||
var
|
||||
changed = false
|
||||
transProto = IpTransportProtocol.udpProtocol
|
||||
transProtoPort: Port
|
||||
|
||||
var updated: MultiAddress
|
||||
|
||||
if r.data.addresses.len == 0:
|
||||
changed = true
|
||||
if ip.isNone:
|
||||
return err "No existing address in SignedPeerRecord with no IP provided"
|
||||
|
||||
if udpPort.isNone and tcpPort.isNone:
|
||||
return err "No existing address in SignedPeerRecord with no port provided"
|
||||
|
||||
let ipAddr = try: ValidIpAddress.init(ip.get)
|
||||
except ValueError as e:
|
||||
return err ("Existing address contains invalid address: " & $e.msg).cstring
|
||||
if tcpPort.isSome:
|
||||
transProto = IpTransportProtocol.tcpProtocol
|
||||
transProtoPort = tcpPort.get
|
||||
if udpPort.isSome:
|
||||
transProto = IpTransportProtocol.udpProtocol
|
||||
transProtoPort = udpPort.get
|
||||
|
||||
updated = MultiAddress.init(ipAddr, transProto, transProtoPort)
|
||||
|
||||
else:
|
||||
let
|
||||
existing = r.data.addresses[0].address
|
||||
existingNetProto = ? existing[0].mapErr((e: string) => e.cstring)
|
||||
existingTransProto = ? existing[1].mapErr((e: string) => e.cstring)
|
||||
existingNetProtoFam = ? existingNetProto.protoCode
|
||||
.mapErr((e: string) => e.cstring)
|
||||
existingNetProtoAddr = ? existingNetProto.protoAddress
|
||||
.mapErr((e: string) => e.cstring)
|
||||
existingTransProtoCodec = ? existingTransProto.protoCode
|
||||
.mapErr((e: string) => e.cstring)
|
||||
existingTransProtoPort = ? existingTransProto.protoAddress
|
||||
.mapErr((e: string) => e.cstring)
|
||||
existingIp =
|
||||
if existingNetProtoFam == MultiCodec.codec("ip6"):
|
||||
ipv6 array[16, byte].initCopyFrom(existingNetProtoAddr)
|
||||
else:
|
||||
ipv4 array[4, byte].initCopyFrom(existingNetProtoAddr)
|
||||
|
||||
ipAddr = ip.get(existingIp)
|
||||
|
||||
|
||||
if tcpPort.isNone and udpPort.isNone:
|
||||
transProto =
|
||||
if existingTransProtoCodec == MultiCodec.codec("udp"):
|
||||
IpTransportProtocol.udpProtocol
|
||||
else: IpTransportProtocol.tcpProtocol
|
||||
transProtoPort = Port(uint16.fromBytesBE(existingTransProtoPort))
|
||||
|
||||
else:
|
||||
if tcpPort.isSome:
|
||||
transProto = IpTransportProtocol.tcpProtocol
|
||||
transProtoPort = tcpPort.get
|
||||
if udpPort.isSome:
|
||||
transProto = IpTransportProtocol.udpProtocol
|
||||
transProtoPort = udpPort.get
|
||||
|
||||
updated = MultiAddress.init(ipAddr, transProto, transProtoPort)
|
||||
changed = existing != updated
|
||||
|
||||
r.data.addresses[0].address = updated
|
||||
|
||||
# increase the sequence number only if we've updated the multiaddress
|
||||
if changed: r.data.seqNo.inc()
|
||||
|
||||
r = ? SignedPeerRecord.init(pk, r.data)
|
||||
.mapErr((e: CryptoError) =>
|
||||
("Failed to update SignedPeerRecord: " & $e).cstring
|
||||
)
|
||||
|
||||
return ok()
|
||||
|
||||
proc update*(r: var SignedPeerRecord, pk: keys.PrivateKey,
|
||||
ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port] = none[Port]()):
|
||||
RecordResult[void] =
|
||||
let cPk = pkToPk(pk).get
|
||||
r.update(cPk, ip, tcpPort, udpPort)
|
||||
|
||||
proc toTypedRecord*(r: SignedPeerRecord) : RecordResult[SignedPeerRecord] = ok(r)
|
||||
|
||||
proc ip*(r: SignedPeerRecord): Option[array[4, byte]] =
|
||||
let ma = r.data.addresses[0].address
|
||||
|
||||
let code = ma[0].get.protoCode()
|
||||
if code.isOk and code.get == multiCodec("ip4"):
|
||||
var ipbuf: array[4, byte]
|
||||
let res = ma[0].get.protoArgument(ipbuf)
|
||||
if res.isOk:
|
||||
return some(ipbuf)
|
||||
|
||||
# err("Incorrect IPv4 address")
|
||||
# else:
|
||||
# if (?(?ma[1]).protoArgument(pbuf)) == 0:
|
||||
# err("Incorrect port number")
|
||||
# else:
|
||||
# res.port = Port(fromBytesBE(uint16, pbuf))
|
||||
# ok(res)
|
||||
# else:
|
||||
|
||||
# else:
|
||||
# err("MultiAddress must be wire address (tcp, udp or unix)")
|
||||
|
||||
proc udp*(r: SignedPeerRecord): Option[int] =
|
||||
let ma = r.data.addresses[0].address
|
||||
|
||||
let code = ma[1].get.protoCode()
|
||||
if code.isOk and code.get == multiCodec("udp"):
|
||||
var pbuf: array[2, byte]
|
||||
let res = ma[1].get.protoArgument(pbuf)
|
||||
if res.isOk:
|
||||
let p = fromBytesBE(uint16, pbuf)
|
||||
return some(p.int)
|
||||
|
||||
proc fromBase64*(r: var SignedPeerRecord, s: string): bool =
|
||||
## Loads SPR from base64-encoded rlp-encoded bytes, and validates the
|
||||
## signature.
|
||||
let bytes = Base64Url.decode(s)
|
||||
r.fromBytes(bytes)
|
||||
|
||||
proc fromURI*(r: var SignedPeerRecord, s: string): bool =
|
||||
## Loads SignedPeerRecord from its text encoding. Validates the signature.
|
||||
## TODO
|
||||
const prefix = "spr:"
|
||||
if s.startsWith(prefix):
|
||||
result = r.fromBase64(s[prefix.len .. ^1])
|
||||
|
||||
template fromURI*(r: var SignedPeerRecord, url: SprUri): bool =
|
||||
fromURI(r, string(url))
|
||||
|
||||
proc toBase64*(r: SignedPeerRecord): string =
|
||||
let encoded = r.encode
|
||||
if encoded.isErr:
|
||||
error "Failed to encode SignedPeerRecord", error = encoded.error
|
||||
result = Base64Url.encode(encoded.get(@[]))
|
||||
|
||||
proc toURI*(r: SignedPeerRecord): string = "spr:" & r.toBase64
|
||||
|
||||
proc init*(T: type SignedPeerRecord, seqNum: uint64,
|
||||
pk: crypto.PrivateKey,
|
||||
ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port]):
|
||||
RecordResult[T] =
|
||||
## Initialize a `SignedPeerRecord` with given sequence number, private key, optional
|
||||
## ip address, tcp port, udp port, and optional custom k:v pairs.
|
||||
##
|
||||
## Can fail in case the record exceeds the `maxSprSize`.
|
||||
|
||||
let peerId = PeerId.init(pk).get
|
||||
|
||||
if tcpPort.isSome() and udpPort.isSome:
|
||||
warn "Both tcp and udp ports specified, using udp in multiaddress",
|
||||
tcpPort, udpPort
|
||||
|
||||
var
|
||||
ipAddr = try: ValidIpAddress.init("127.0.0.1")
|
||||
except ValueError as e:
|
||||
return err ("Existing address contains invalid address: " & $e.msg).cstring
|
||||
proto: IpTransportProtocol
|
||||
protoPort: Port
|
||||
|
||||
if ip.isSome():
|
||||
|
||||
ipAddr = ip.get
|
||||
|
||||
if tcpPort.isSome():
|
||||
proto = IpTransportProtocol.tcpProtocol
|
||||
protoPort = tcpPort.get()
|
||||
if udpPort.isSome():
|
||||
proto = IpTransportProtocol.udpProtocol
|
||||
protoPort = udpPort.get()
|
||||
else:
|
||||
if tcpPort.isSome():
|
||||
proto = IpTransportProtocol.tcpProtocol
|
||||
protoPort = tcpPort.get()
|
||||
if udpPort.isSome():
|
||||
proto = IpTransportProtocol.udpProtocol
|
||||
protoPort = udpPort.get()
|
||||
|
||||
|
||||
let ma = MultiAddress.init(ipAddr, proto, protoPort)
|
||||
# if ip.isSome:
|
||||
# let
|
||||
# ipAddr = ip.get
|
||||
# proto = ipAddr.family
|
||||
# address = if proto == IPv4: ipAddr.address_v4
|
||||
# else: ipAddr.address_v6
|
||||
# u and udpPort.isSome
|
||||
# # let ta = initTAddress(ip.get, udpPort.get)
|
||||
# # echo ta
|
||||
# # ma = MultiAddress.init(ta).get
|
||||
# #let ma1 = MultiAddress.init("/ip4/127.0.0.1").get() #TODO
|
||||
# #let ma2 = MultiAddress.init(multiCodec("udp"), udpPort.get.int).get
|
||||
# #ma = ma1 & ma2
|
||||
# ma = MultiAddress.init("/ip4/127.0.0.1/udp/" & $udpPort.get.int).get #TODO
|
||||
# else:
|
||||
# ma = MultiAddress.init()
|
||||
# # echo "not implemented"
|
||||
|
||||
let pr = PeerRecord.init(peerId, @[ma], seqNum)
|
||||
SignedPeerRecord.init(pk, pr).mapErr((e: CryptoError) => ("Failed to init SignedPeerRecord: " & $e).cstring)
|
||||
|
||||
proc init*(T: type SignedPeerRecord, seqNum: uint64,
|
||||
pk: keys.PrivateKey,
|
||||
ip: Option[ValidIpAddress],
|
||||
tcpPort, udpPort: Option[Port]):
|
||||
RecordResult[T] =
|
||||
let kPk = pkToPk(pk).get
|
||||
SignedPeerRecord.init(seqNum, kPk, ip, tcpPort, udpPort)
|
||||
|
||||
proc contains*(r: SignedPeerRecord, fp: (string, seq[byte])): bool =
|
||||
# TODO: use FieldPair for this, but that is a bit cumbersome. Perhaps the
|
||||
# `get` call can be improved to make this easier.
|
||||
# let field = r.tryGet(fp[0], seq[byte])
|
||||
# if field.isSome():
|
||||
# if field.get() == fp[1]:
|
||||
# return true
|
||||
# TODO: Implement if SignedPeerRecord custom field pairs are implemented
|
||||
debugEcho "`contains` is not yet implemented for SignedPeerRecords"
|
||||
return false
|
||||
|
||||
proc `==`*(a, b: SignedPeerRecord): bool = a.data == b.data
|
@ -24,7 +24,7 @@ type
|
||||
bindAddress: Address ## UDP binding address
|
||||
transp: DatagramTransport
|
||||
pendingRequests: Table[AESGCMNonce, PendingRequest]
|
||||
codec*: Codec
|
||||
codec*: Codec
|
||||
rng: ref BrHmacDrbgContext
|
||||
|
||||
PendingRequest = object
|
||||
@ -135,12 +135,12 @@ proc receive*(t: Transport, a: Address, packet: openArray[byte]) =
|
||||
trace "Received handshake message packet", srcId = packet.srcIdHs,
|
||||
address = a, kind = packet.message.kind
|
||||
t.client.handleMessage(packet.srcIdHs, a, packet.message)
|
||||
# For a handshake message it is possible that we received an newer ENR.
|
||||
# For a handshake message it is possible that we received an newer SPR.
|
||||
# In that case we can add/update it to the routing table.
|
||||
if packet.node.isSome():
|
||||
let node = packet.node.get()
|
||||
# Lets not add nodes without correct IP in the ENR to the routing table.
|
||||
# The ENR could contain bogus IPs and although they would get removed
|
||||
# Lets not add nodes without correct IP in the SPR to the routing table.
|
||||
# The SPR could contain bogus IPs and although they would get removed
|
||||
# on the next revalidation, one could spam these as the handshake
|
||||
# message occurs on (first) incoming messages.
|
||||
if node.address.isSome() and a == node.address.get():
|
||||
|
@ -1,8 +1,9 @@
|
||||
import
|
||||
stew/shims/net, bearssl, chronos,
|
||||
eth/keys,
|
||||
libp2pdht/discv5/[enr, node, routing_table],
|
||||
libp2pdht/discv5/[spr, node, routing_table],
|
||||
libp2pdht/discv5/protocol as discv5_protocol,
|
||||
libp2p/crypto/crypto,
|
||||
libp2p/multiaddress
|
||||
|
||||
export net
|
||||
@ -12,11 +13,11 @@ proc localAddress*(port: int): Address =
|
||||
|
||||
proc initDiscoveryNode*(
|
||||
rng: ref BrHmacDrbgContext,
|
||||
privKey: PrivateKey,
|
||||
privKey: keys.PrivateKey,
|
||||
address: Address,
|
||||
bootstrapRecords: openArray[Record] = [],
|
||||
bootstrapRecords: openArray[SignedPeerRecord] = [],
|
||||
localEnrFields: openArray[(string, seq[byte])] = [],
|
||||
previousRecord = none[enr.Record]()):
|
||||
previousRecord = none[SignedPeerRecord]()):
|
||||
discv5_protocol.Protocol =
|
||||
# set bucketIpLimit to allow bucket split
|
||||
let config = DiscoveryConfig.init(1000, 24, 5)
|
||||
@ -40,21 +41,53 @@ proc nodeIdInNodes*(id: NodeId, nodes: openArray[Node]): bool =
|
||||
for n in nodes:
|
||||
if id == n.id: return true
|
||||
|
||||
proc generateNode*(privKey: PrivateKey, port: int = 20302,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1"),
|
||||
localEnrFields: openArray[FieldPair] = []): Node =
|
||||
let port = Port(port)
|
||||
let enr = enr.Record.init(1, privKey, some(ip),
|
||||
some(port), some(port), localEnrFields).expect("Properly intialized private key")
|
||||
result = newNode(enr).expect("Properly initialized node")
|
||||
proc generateNode*(privKey: keys.PrivateKey, port: int = 20302,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): Node =
|
||||
let
|
||||
port = Port(port)
|
||||
spr = SignedPeerRecord.init(1, privKey, some(ip), some(port), some(port))
|
||||
.expect("Properly intialized private key")
|
||||
result = newNode(spr).expect("Properly initialized node")
|
||||
|
||||
proc generateNRandomNodes*(rng: ref BrHmacDrbgContext, n: int): seq[Node] =
|
||||
var res = newSeq[Node]()
|
||||
for i in 1..n:
|
||||
let node = generateNode(PrivateKey.random(rng[]))
|
||||
let node = generateNode(keys.PrivateKey.random(rng[]))
|
||||
res.add(node)
|
||||
res
|
||||
|
||||
proc nodeAndPrivKeyAtDistance*(n: Node, rng: var BrHmacDrbgContext, d: uint32,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): (Node, keys.PrivateKey) =
|
||||
while true:
|
||||
let pk = keys.PrivateKey.random(rng)
|
||||
let node = generateNode(pk, ip = ip)
|
||||
if logDistance(n.id, node.id) == d:
|
||||
return (node, pk)
|
||||
|
||||
proc nodeAtDistance*(n: Node, rng: var BrHmacDrbgContext, d: uint32,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): Node =
|
||||
let (node, _) = n.nodeAndPrivKeyAtDistance(rng, d, ip)
|
||||
node
|
||||
|
||||
proc nodesAtDistance*(
|
||||
n: Node, rng: var BrHmacDrbgContext, d: uint32, amount: int,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): seq[Node] =
|
||||
for i in 0..<amount:
|
||||
result.add(nodeAtDistance(n, rng, d, ip))
|
||||
|
||||
proc nodesAtDistanceUniqueIp*(
|
||||
n: Node, rng: var BrHmacDrbgContext, d: uint32, amount: int,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): seq[Node] =
|
||||
var ta = initTAddress(ip, Port(0))
|
||||
for i in 0..<amount:
|
||||
ta.inc()
|
||||
result.add(nodeAtDistance(n, rng, d, ValidIpAddress.init(ta.address())))
|
||||
|
||||
proc addSeenNode*(d: discv5_protocol.Protocol, n: Node): bool =
|
||||
# Add it as a seen node, warning: for testing convenience only!
|
||||
n.seen = true
|
||||
d.addNode(n)
|
||||
|
||||
func udpExample*(_: type MultiAddress): MultiAddress =
|
||||
## creates a new udp multiaddress on a random port
|
||||
Multiaddress.init("/ip4/0.0.0.0/udp/0")
|
||||
@ -64,3 +97,19 @@ func udpExamples*(_: type MultiAddress, count: int): seq[MultiAddress] =
|
||||
for i in 1..count:
|
||||
res.add Multiaddress.init("/ip4/0.0.0.0/udp/" & $i).get
|
||||
return res
|
||||
|
||||
proc toSignedPeerRecord*(privKey: crypto.PrivateKey) : SignedPeerRecord =
|
||||
## handle conversion between the two worlds
|
||||
|
||||
let pr = PeerRecord.init(
|
||||
peerId = PeerId.init(privKey.getPublicKey.get).get,
|
||||
addresses = MultiAddress.udpExamples(3))
|
||||
return SignedPeerRecord.init(privKey, pr)
|
||||
.expect("Should init SignedPeerRecord with private key")
|
||||
|
||||
proc example*(T: type SignedPeerRecord): T =
|
||||
let
|
||||
rng = crypto.newRng()
|
||||
privKey = crypto.PrivateKey.random(rng[]).expect("Valid rng")
|
||||
|
||||
privKey.toSignedPeerRecord
|
||||
|
@ -26,22 +26,9 @@ import
|
||||
libp2p/multicodec,
|
||||
libp2p/signed_envelope
|
||||
|
||||
|
||||
|
||||
# suite "Providers Tests: node alone":
|
||||
|
||||
proc toSignedPeerRecord(privKey: crypto.PrivateKey) : SignedPeerRecord =
|
||||
## handle conversion between the two worlds
|
||||
|
||||
let pr = PeerRecord.init(
|
||||
peerId = PeerId.init(privKey.getPublicKey.get).get,
|
||||
addresses = MultiAddress.udpExamples(3))
|
||||
return SignedPeerRecord.init(privKey, pr).expect("Should init SignedPeerRecord with private key")
|
||||
# trace "IDs", discNodeId, digest, mh, peerId=result.peerId.hex
|
||||
|
||||
proc bootstrapNodes(
|
||||
nodecount: int,
|
||||
bootnodes: openArray[Record],
|
||||
bootnodes: openArray[SignedPeerRecord],
|
||||
rng = keys.newRng()
|
||||
) : seq[(discv5_protocol.Protocol, keys.PrivateKey)] =
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
import
|
||||
stew/shims/net, bearssl, chronos,
|
||||
eth/keys,
|
||||
libp2pdht/discv5/[enr, node, routing_table],
|
||||
libp2pdht/discv5/protocol as discv5_protocol
|
||||
|
||||
export net
|
||||
|
||||
proc localAddress*(port: int): Address =
|
||||
Address(ip: ValidIpAddress.init("127.0.0.1"), port: Port(port))
|
||||
|
||||
proc initDiscoveryNode*(
|
||||
rng: ref BrHmacDrbgContext,
|
||||
privKey: PrivateKey,
|
||||
address: Address,
|
||||
bootstrapRecords: openArray[Record] = [],
|
||||
localEnrFields: openArray[(string, seq[byte])] = [],
|
||||
previousRecord = none[enr.Record]()):
|
||||
discv5_protocol.Protocol =
|
||||
# set bucketIpLimit to allow bucket split
|
||||
let config = DiscoveryConfig.init(1000, 24, 5)
|
||||
|
||||
let protocol = newProtocol(
|
||||
privKey,
|
||||
some(address.ip),
|
||||
some(address.port), some(address.port),
|
||||
bindPort = address.port,
|
||||
bootstrapRecords = bootstrapRecords,
|
||||
localEnrFields = localEnrFields,
|
||||
previousRecord = previousRecord,
|
||||
config = config,
|
||||
rng = rng)
|
||||
|
||||
protocol.open()
|
||||
|
||||
protocol
|
||||
|
||||
proc nodeIdInNodes*(id: NodeId, nodes: openArray[Node]): bool =
|
||||
for n in nodes:
|
||||
if id == n.id: return true
|
||||
|
||||
proc generateNode*(privKey: PrivateKey, port: int = 20302,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1"),
|
||||
localEnrFields: openArray[FieldPair] = []): Node =
|
||||
let port = Port(port)
|
||||
let enr = enr.Record.init(1, privKey, some(ip),
|
||||
some(port), some(port), localEnrFields).expect("Properly intialized private key")
|
||||
result = newNode(enr).expect("Properly initialized node")
|
||||
|
||||
proc generateNRandomNodes*(rng: ref BrHmacDrbgContext, n: int): seq[Node] =
|
||||
var res = newSeq[Node]()
|
||||
for i in 1..n:
|
||||
let node = generateNode(PrivateKey.random(rng[]))
|
||||
res.add(node)
|
||||
res
|
||||
|
||||
proc nodeAndPrivKeyAtDistance*(n: Node, rng: var BrHmacDrbgContext, d: uint32,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): (Node, PrivateKey) =
|
||||
while true:
|
||||
let pk = PrivateKey.random(rng)
|
||||
let node = generateNode(pk, ip = ip)
|
||||
if logDistance(n.id, node.id) == d:
|
||||
return (node, pk)
|
||||
|
||||
proc nodeAtDistance*(n: Node, rng: var BrHmacDrbgContext, d: uint32,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): Node =
|
||||
let (node, _) = n.nodeAndPrivKeyAtDistance(rng, d, ip)
|
||||
node
|
||||
|
||||
proc nodesAtDistance*(
|
||||
n: Node, rng: var BrHmacDrbgContext, d: uint32, amount: int,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): seq[Node] =
|
||||
for i in 0..<amount:
|
||||
result.add(nodeAtDistance(n, rng, d, ip))
|
||||
|
||||
proc nodesAtDistanceUniqueIp*(
|
||||
n: Node, rng: var BrHmacDrbgContext, d: uint32, amount: int,
|
||||
ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): seq[Node] =
|
||||
var ta = initTAddress(ip, Port(0))
|
||||
for i in 0..<amount:
|
||||
ta.inc()
|
||||
result.add(nodeAtDistance(n, rng, d, ValidIpAddress.init(ta.address())))
|
||||
|
||||
proc addSeenNode*(d: discv5_protocol.Protocol, n: Node): bool =
|
||||
# Add it as a seen node, warning: for testing convenience only!
|
||||
n.seen = true
|
||||
d.addNode(n)
|
@ -5,26 +5,26 @@ import
|
||||
chronos, chronicles, stint, asynctest, stew/shims/net,
|
||||
stew/byteutils, bearssl,
|
||||
eth/keys,
|
||||
libp2pdht/discv5/[transport, enr, node, routing_table, encoding, sessions, messages, nodes_verification],
|
||||
libp2pdht/discv5/[transport, spr, node, routing_table, encoding, sessions, messages, nodes_verification],
|
||||
libp2pdht/discv5/protocol as discv5_protocol,
|
||||
./discv5_test_helper
|
||||
../dht/test_helper
|
||||
|
||||
suite "Discovery v5 Tests":
|
||||
var rng: ref HmacDrbgContext
|
||||
|
||||
setup:
|
||||
rng = newRng()
|
||||
rng = keys.newRng()
|
||||
|
||||
test "GetNode":
|
||||
# TODO: This could be tested in just a routing table only context
|
||||
let
|
||||
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
targetNode = generateNode(PrivateKey.random(rng[]))
|
||||
node = initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
targetNode = generateNode(keys.PrivateKey.random(rng[]))
|
||||
|
||||
check node.addNode(targetNode)
|
||||
|
||||
for i in 0..<1000:
|
||||
discard node.addNode(generateNode(PrivateKey.random(rng[])))
|
||||
discard node.addNode(generateNode(keys.PrivateKey.random(rng[])))
|
||||
|
||||
let n = node.getNode(targetNode.id)
|
||||
check n.isSome()
|
||||
@ -35,12 +35,12 @@ suite "Discovery v5 Tests":
|
||||
test "Node deletion":
|
||||
let
|
||||
bootnode = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302),
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302),
|
||||
@[bootnode.localNode.record])
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20303),
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20303),
|
||||
@[bootnode.localNode.record])
|
||||
pong1 = await discv5_protocol.ping(node1, bootnode.localNode)
|
||||
pong2 = await discv5_protocol.ping(node1, node2.localNode)
|
||||
@ -136,10 +136,10 @@ suite "Discovery v5 Tests":
|
||||
("e24a7bc9051058f918646b0f6e3d16884b2a55a15553b89bab910d55ebc36116", 256'u16)
|
||||
]
|
||||
|
||||
let targetId = toNodeId(PublicKey.fromHex(targetKey)[])
|
||||
let targetId = toNodeId(keys.PublicKey.fromHex(targetKey)[])
|
||||
|
||||
for (key, d) in testValues:
|
||||
let id = toNodeId(PrivateKey.fromHex(key)[].toPublicKey())
|
||||
let id = toNodeId(keys.PrivateKey.fromHex(key)[].toPublicKey())
|
||||
check logDistance(targetId, id) == d
|
||||
|
||||
test "Distance to id check":
|
||||
@ -170,7 +170,7 @@ suite "Discovery v5 Tests":
|
||||
("1a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 256'u16)
|
||||
]
|
||||
|
||||
let targetId = toNodeId(PublicKey.fromHex(targetKey)[])
|
||||
let targetId = toNodeId(keys.PublicKey.fromHex(targetKey)[])
|
||||
|
||||
for (id, d) in testValues:
|
||||
check idAtDistance(targetId, d) == parse(id, UInt256, 16)
|
||||
@ -178,9 +178,9 @@ suite "Discovery v5 Tests":
|
||||
test "FindNode Test":
|
||||
const dist = 253'u16
|
||||
let
|
||||
mainNodeKey = PrivateKey.fromHex(
|
||||
mainNodeKey = keys.PrivateKey.fromHex(
|
||||
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
|
||||
testNodeKey = PrivateKey.fromHex(
|
||||
testNodeKey = keys.PrivateKey.fromHex(
|
||||
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618")[]
|
||||
mainNode = initDiscoveryNode(rng, mainNodeKey, localAddress(20301))
|
||||
testNode = initDiscoveryNode(rng, testNodeKey, localAddress(20302))
|
||||
@ -194,14 +194,14 @@ suite "Discovery v5 Tests":
|
||||
check (await testNode.ping(mainNode.localNode)).isOk()
|
||||
check (await mainNode.ping(testNode.localNode)).isOk()
|
||||
|
||||
# Get ENR of the node itself
|
||||
# Get SPR of the node itself
|
||||
var discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[0'u16])
|
||||
check:
|
||||
discovered.isOk
|
||||
discovered[].len == 1
|
||||
discovered[][0] == mainNode.localNode
|
||||
# Get ENRs of nodes added at provided logarithmic distance
|
||||
# Get SPRs of nodes added at provided logarithmic distance
|
||||
discovered =
|
||||
await findNode(testNode, mainNode.localNode, @[dist])
|
||||
check discovered.isOk
|
||||
@ -246,11 +246,11 @@ suite "Discovery v5 Tests":
|
||||
test "FindNode with test table":
|
||||
|
||||
let mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
|
||||
# Generate 1000 random nodes and add to our main node's routing table
|
||||
for i in 0..<1000:
|
||||
discard mainNode.addSeenNode(generateNode(PrivateKey.random(rng[]))) # for testing only!
|
||||
discard mainNode.addSeenNode(generateNode(keys.PrivateKey.random(rng[]))) # for testing only!
|
||||
|
||||
let
|
||||
neighbours = mainNode.neighbours(mainNode.localNode.id)
|
||||
@ -261,7 +261,7 @@ suite "Discovery v5 Tests":
|
||||
|
||||
let
|
||||
testNode = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302),
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302),
|
||||
@[mainNode.localNode.record])
|
||||
discovered = await findNode(testNode, mainNode.localNode,
|
||||
@[closestDistance])
|
||||
@ -277,13 +277,13 @@ suite "Discovery v5 Tests":
|
||||
nodeCount = 17
|
||||
|
||||
let bootNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
bootNode.start()
|
||||
|
||||
var nodes = newSeqOfCap[discv5_protocol.Protocol](nodeCount)
|
||||
nodes.add(bootNode)
|
||||
for i in 1 ..< nodeCount:
|
||||
nodes.add(initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301 + i),
|
||||
nodes.add(initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301 + i),
|
||||
@[bootNode.localNode.record]))
|
||||
|
||||
# Make sure all nodes have "seen" each other by forcing pings
|
||||
@ -311,10 +311,10 @@ suite "Discovery v5 Tests":
|
||||
test "Resolve target":
|
||||
let
|
||||
mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
lookupNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
targetKey = PrivateKey.random(rng[])
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
targetKey = keys.PrivateKey.random(rng[])
|
||||
targetAddress = localAddress(20303)
|
||||
targetNode = initDiscoveryNode(rng, targetKey, targetAddress)
|
||||
targetId = targetNode.localNode.id
|
||||
@ -334,12 +334,12 @@ suite "Discovery v5 Tests":
|
||||
n.get().record.seqNum == targetSeqNum
|
||||
# Node will be removed because of failed findNode request.
|
||||
|
||||
# Bring target back online, update seqNum in ENR, check if we get the
|
||||
# updated ENR.
|
||||
# Bring target back online, update seqNum in SPR, check if we get the
|
||||
# updated SPR.
|
||||
block:
|
||||
targetNode.open()
|
||||
# Request the target ENR and manually add it to the routing table.
|
||||
# Ping for handshake based ENR passing will not work as our previous
|
||||
# Request the target SPR and manually add it to the routing table.
|
||||
# Ping for handshake based SPR passing will not work as our previous
|
||||
# session will still be in the LRU cache.
|
||||
let nodes = await mainNode.findNode(targetNode.localNode, @[0'u16])
|
||||
check:
|
||||
@ -348,8 +348,8 @@ suite "Discovery v5 Tests":
|
||||
mainNode.addNode(nodes[][0])
|
||||
|
||||
targetSeqNum.inc()
|
||||
# need to add something to get the enr sequence number incremented
|
||||
let update = targetNode.updateRecord({"addsomefield": @[byte 1]})
|
||||
# need to add something to get the spr sequence number incremented
|
||||
let update = targetNode.updateRecord()
|
||||
check update.isOk()
|
||||
|
||||
var n = mainNode.getNode(targetId)
|
||||
@ -367,14 +367,14 @@ suite "Discovery v5 Tests":
|
||||
# Add the updated version
|
||||
discard mainNode.addNode(n.get())
|
||||
|
||||
# Update seqNum in ENR again, ping lookupNode to be added in routing table,
|
||||
# close targetNode, resolve should lookup, check if we get updated ENR.
|
||||
# Update seqNum in SPR again, ping lookupNode to be added in routing table,
|
||||
# close targetNode, resolve should lookup, check if we get updated SPR.
|
||||
block:
|
||||
targetSeqNum.inc()
|
||||
let update = targetNode.updateRecord({"addsomefield": @[byte 2]})
|
||||
let update = targetNode.updateRecord()
|
||||
check update.isOk()
|
||||
|
||||
# ping node so that its ENR gets added
|
||||
# ping node so that its SPR gets added
|
||||
check (await targetNode.ping(lookupNode.localNode)).isOk()
|
||||
# ping node so that it becomes "seen" and thus will be forwarded on a
|
||||
# findNode request
|
||||
@ -391,31 +391,30 @@ suite "Discovery v5 Tests":
|
||||
await mainNode.closeWait()
|
||||
await lookupNode.closeWait()
|
||||
|
||||
test "Random nodes with enr field filter":
|
||||
# We no longer support field filtering
|
||||
# test "Random nodes with spr field filter":
|
||||
# let
|
||||
# lookupNode = initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
# targetNode = generateNode(keys.PrivateKey.random(rng[]))
|
||||
# otherNode = generateNode(keys.PrivateKey.random(rng[]))
|
||||
# anotherNode = generateNode(keys.PrivateKey.random(rng[]))
|
||||
|
||||
# check:
|
||||
# lookupNode.addNode(targetNode)
|
||||
# lookupNode.addNode(otherNode)
|
||||
# lookupNode.addNode(anotherNode)
|
||||
|
||||
# let discovered = lookupNode.randomNodes(10)
|
||||
# check discovered.len == 3
|
||||
# let discoveredFiltered = lookupNode.randomNodes(10,
|
||||
# ("test", @[byte 1,2,3,4]))
|
||||
# check discoveredFiltered.len == 1 and discoveredFiltered.contains(targetNode)
|
||||
|
||||
# await lookupNode.closeWait()
|
||||
|
||||
test "New protocol with spr":
|
||||
let
|
||||
lookupNode = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
targetFieldPair = toFieldPair("test", @[byte 1,2,3,4])
|
||||
targetNode = generateNode(PrivateKey.random(rng[]), localEnrFields = [targetFieldPair])
|
||||
otherFieldPair = toFieldPair("test", @[byte 1,2,3,4,5])
|
||||
otherNode = generateNode(PrivateKey.random(rng[]), localEnrFields = [otherFieldPair])
|
||||
anotherNode = generateNode(PrivateKey.random(rng[]))
|
||||
|
||||
check:
|
||||
lookupNode.addNode(targetNode)
|
||||
lookupNode.addNode(otherNode)
|
||||
lookupNode.addNode(anotherNode)
|
||||
|
||||
let discovered = lookupNode.randomNodes(10)
|
||||
check discovered.len == 3
|
||||
let discoveredFiltered = lookupNode.randomNodes(10,
|
||||
("test", @[byte 1,2,3,4]))
|
||||
check discoveredFiltered.len == 1 and discoveredFiltered.contains(targetNode)
|
||||
|
||||
await lookupNode.closeWait()
|
||||
|
||||
test "New protocol with enr":
|
||||
let
|
||||
privKey = PrivateKey.random(rng[])
|
||||
privKey = keys.PrivateKey.random(rng[])
|
||||
ip = some(ValidIpAddress.init("127.0.0.1"))
|
||||
port = Port(20301)
|
||||
node = newProtocol(privKey, ip, some(port), some(port), bindPort = port,
|
||||
@ -436,23 +435,23 @@ suite "Discovery v5 Tests":
|
||||
|
||||
# Defect (for now?) on incorrect key use
|
||||
expect ResultDefect:
|
||||
let incorrectKeyUpdates = newProtocol(PrivateKey.random(rng[]),
|
||||
let incorrectKeyUpdates = newProtocol(keys.PrivateKey.random(rng[]),
|
||||
ip, some(port), some(port), bindPort = port, rng = rng,
|
||||
previousRecord = some(updatesNode.getRecord()))
|
||||
|
||||
test "Update node record with revalidate":
|
||||
let
|
||||
mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
testNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
testNodeId = testNode.localNode.id
|
||||
|
||||
check:
|
||||
# Get node with current ENR in routing table.
|
||||
# Get node with current SPR in routing table.
|
||||
# Handshake will get done here.
|
||||
(await testNode.ping(mainNode.localNode)).isOk()
|
||||
testNode.updateRecord({"test" : @[byte 1]}).isOk()
|
||||
testNode.updateRecord().isOk()
|
||||
testNode.localNode.record.seqNum == 2
|
||||
|
||||
# Get the node from routing table, seqNum should still be 1.
|
||||
@ -461,7 +460,7 @@ suite "Discovery v5 Tests":
|
||||
n.isSome()
|
||||
n.get.record.seqNum == 1
|
||||
|
||||
# This should not do a handshake and thus the new ENR must come from the
|
||||
# This should not do a handshake and thus the new SPR must come from the
|
||||
# findNode(0)
|
||||
await mainNode.revalidateNode(n.get)
|
||||
|
||||
@ -477,16 +476,16 @@ suite "Discovery v5 Tests":
|
||||
test "Update node record with handshake":
|
||||
let
|
||||
mainNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20301))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301))
|
||||
testNode =
|
||||
initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
testNodeId = testNode.localNode.id
|
||||
|
||||
# Add the node (from the record, so new node!) so no handshake is done yet.
|
||||
check: mainNode.addNode(testNode.localNode.record)
|
||||
|
||||
check:
|
||||
testNode.updateRecord({"test" : @[byte 1]}).isOk()
|
||||
testNode.updateRecord().isOk()
|
||||
testNode.localNode.record.seqNum == 2
|
||||
|
||||
# Get the node from routing table, seqNum should still be 1.
|
||||
@ -495,7 +494,7 @@ suite "Discovery v5 Tests":
|
||||
n.isSome()
|
||||
n.get.record.seqNum == 1
|
||||
|
||||
# This should do a handshake and update the ENR through that.
|
||||
# This should do a handshake and update the SPR through that.
|
||||
check (await testNode.ping(mainNode.localNode)).isOk()
|
||||
|
||||
# Get the node from routing table, and check if record got updated.
|
||||
@ -510,17 +509,17 @@ suite "Discovery v5 Tests":
|
||||
test "Verify records of nodes message":
|
||||
let
|
||||
port = Port(9000)
|
||||
fromNoderecord = enr.Record.init(1, PrivateKey.random(rng[]),
|
||||
fromNoderecord = SignedPeerRecord.init(1, keys.PrivateKey.random(rng[]),
|
||||
some(ValidIpAddress.init("11.12.13.14")),
|
||||
some(port), some(port))[]
|
||||
fromNode = newNode(fromNoderecord)[]
|
||||
pk = PrivateKey.random(rng[])
|
||||
pk = keys.PrivateKey.random(rng[])
|
||||
targetDistance = @[logDistance(fromNode.id, pk.toPublicKey().toNodeId())]
|
||||
limit = 16
|
||||
|
||||
block: # Duplicates
|
||||
let
|
||||
record = enr.Record.init(
|
||||
record = SignedPeerRecord.init(
|
||||
1, pk, some(ValidIpAddress.init("12.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
|
||||
@ -530,7 +529,7 @@ suite "Discovery v5 Tests":
|
||||
check nodes.len == 1
|
||||
|
||||
# Node id duplicates
|
||||
let recordSameId = enr.Record.init(
|
||||
let recordSameId = SignedPeerRecord.init(
|
||||
1, pk, some(ValidIpAddress.init("212.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
records.add(recordSameId)
|
||||
@ -539,7 +538,7 @@ suite "Discovery v5 Tests":
|
||||
|
||||
block: # No address
|
||||
let
|
||||
recordNoAddress = enr.Record.init(
|
||||
recordNoAddress = SignedPeerRecord.init(
|
||||
1, pk, none(ValidIpAddress), some(port), some(port))[]
|
||||
records = [recordNoAddress]
|
||||
test = verifyNodesRecords(records, fromNode, limit, targetDistance)
|
||||
@ -547,7 +546,7 @@ suite "Discovery v5 Tests":
|
||||
|
||||
block: # Invalid address - site local
|
||||
let
|
||||
recordInvalidAddress = enr.Record.init(
|
||||
recordInvalidAddress = SignedPeerRecord.init(
|
||||
1, pk, some(ValidIpAddress.init("10.1.2.3")),
|
||||
some(port), some(port))[]
|
||||
records = [recordInvalidAddress]
|
||||
@ -556,7 +555,7 @@ suite "Discovery v5 Tests":
|
||||
|
||||
block: # Invalid address - loopback
|
||||
let
|
||||
recordInvalidAddress = enr.Record.init(
|
||||
recordInvalidAddress = SignedPeerRecord.init(
|
||||
1, pk, some(ValidIpAddress.init("127.0.0.1")),
|
||||
some(port), some(port))[]
|
||||
records = [recordInvalidAddress]
|
||||
@ -565,7 +564,7 @@ suite "Discovery v5 Tests":
|
||||
|
||||
block: # Invalid distance
|
||||
let
|
||||
recordInvalidDistance = enr.Record.init(
|
||||
recordInvalidDistance = SignedPeerRecord.init(
|
||||
1, pk, some(ValidIpAddress.init("12.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
records = [recordInvalidDistance]
|
||||
@ -574,7 +573,7 @@ suite "Discovery v5 Tests":
|
||||
|
||||
block: # Invalid distance but distance validation is disabled
|
||||
let
|
||||
recordInvalidDistance = enr.Record.init(
|
||||
recordInvalidDistance = SignedPeerRecord.init(
|
||||
1, pk, some(ValidIpAddress.init("12.13.14.15")),
|
||||
some(port), some(port))[]
|
||||
records = [recordInvalidDistance]
|
||||
@ -593,15 +592,15 @@ suite "Discovery v5 Tests":
|
||||
test "Handshake cleanup: different ids":
|
||||
# Node to test the handshakes on.
|
||||
let receiveNode = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
|
||||
# Create random packets with same ip but different node ids
|
||||
# and "receive" them on receiveNode
|
||||
let a = localAddress(20303)
|
||||
for i in 0 ..< 5:
|
||||
let
|
||||
privKey = PrivateKey.random(rng[])
|
||||
enrRec = enr.Record.init(1, privKey,
|
||||
privKey = keys.PrivateKey.random(rng[])
|
||||
enrRec = SignedPeerRecord.init(1, privKey,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
sendNode = newNode(enrRec).expect("Properly initialized record")
|
||||
@ -624,13 +623,13 @@ suite "Discovery v5 Tests":
|
||||
test "Handshake cleanup: different ips":
|
||||
# Node to test the handshakes on.
|
||||
let receiveNode = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
|
||||
# Create random packets with same node ids but different ips
|
||||
# and "receive" them on receiveNode
|
||||
let
|
||||
privKey = PrivateKey.random(rng[])
|
||||
enrRec = enr.Record.init(1, privKey,
|
||||
privKey = keys.PrivateKey.random(rng[])
|
||||
enrRec = SignedPeerRecord.init(1, privKey,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
sendNode = newNode(enrRec).expect("Properly initialized record")
|
||||
@ -654,14 +653,14 @@ suite "Discovery v5 Tests":
|
||||
test "Handshake duplicates":
|
||||
# Node to test the handshakes on.
|
||||
let receiveNode = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
|
||||
# Create random packets with same node ids and same ips
|
||||
# and "receive" them on receiveNode
|
||||
let
|
||||
a = localAddress(20303)
|
||||
privKey = PrivateKey.random(rng[])
|
||||
enrRec = enr.Record.init(1, privKey,
|
||||
privKey = keys.PrivateKey.random(rng[])
|
||||
enrRec = SignedPeerRecord.init(1, privKey,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
sendNode = newNode(enrRec).expect("Properly initialized record")
|
||||
@ -687,9 +686,9 @@ suite "Discovery v5 Tests":
|
||||
test "Talkreq no protocol":
|
||||
let
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20303))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20303))
|
||||
talkresp = await discv5_protocol.talkReq(node1, node2.localNode,
|
||||
@[byte 0x01], @[])
|
||||
|
||||
@ -703,9 +702,9 @@ suite "Discovery v5 Tests":
|
||||
test "Talkreq echo protocol":
|
||||
let
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20303))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20303))
|
||||
talkProtocol = "echo".toBytes()
|
||||
|
||||
proc handler(protocol: TalkProtocol, request: seq[byte], fromId: NodeId, fromUdpAddress: Address): seq[byte]
|
||||
@ -728,9 +727,9 @@ suite "Discovery v5 Tests":
|
||||
test "Talkreq register protocols":
|
||||
let
|
||||
node1 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20302))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20302))
|
||||
node2 = initDiscoveryNode(
|
||||
rng, PrivateKey.random(rng[]), localAddress(20303))
|
||||
rng, keys.PrivateKey.random(rng[]), localAddress(20303))
|
||||
talkProtocol = "echo".toBytes()
|
||||
|
||||
proc handler(protocol: TalkProtocol, request: seq[byte], fromId: NodeId, fromUdpAddress: Address): seq[byte]
|
||||
|
@ -6,17 +6,18 @@ import
|
||||
asynctest/unittest2,
|
||||
stint, stew/byteutils, stew/shims/net,
|
||||
eth/[keys,rlp],
|
||||
libp2pdht/discv5/[messages, messages_encoding, encoding, enr, node, sessions]
|
||||
libp2pdht/discv5/[messages, messages_encoding, encoding, spr, node, sessions],
|
||||
../dht/test_helper
|
||||
|
||||
suite "Discovery v5.1 Protocol Message Encodings":
|
||||
test "Ping Request":
|
||||
let
|
||||
enrSeq = 1'u64
|
||||
p = PingMessage(enrSeq: enrSeq)
|
||||
sprSeq = 1'u64
|
||||
p = PingMessage(sprSeq: sprSeq)
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(p, reqId)
|
||||
check encoded.toHex == "01c20101"
|
||||
check byteutils.toHex(encoded) == "01c20101"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -25,18 +26,18 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
check:
|
||||
message.reqId == reqId
|
||||
message.kind == ping
|
||||
message.ping.enrSeq == enrSeq
|
||||
message.ping.sprSeq == sprSeq
|
||||
|
||||
test "Pong Response":
|
||||
let
|
||||
enrSeq = 1'u64
|
||||
sprSeq = 1'u64
|
||||
ip = IpAddress(family: IpAddressFamily.IPv4, address_v4: [127.byte, 0, 0, 1])
|
||||
port = 5000'u16
|
||||
p = PongMessage(enrSeq: enrSeq, ip: ip, port: port)
|
||||
p = PongMessage(sprSeq: sprSeq, ip: ip, port: port)
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(p, reqId)
|
||||
check encoded.toHex == "02ca0101847f000001821388"
|
||||
check byteutils.toHex(encoded) == "02ca0101847f000001821388"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -45,7 +46,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
check:
|
||||
message.reqId == reqId
|
||||
message.kind == pong
|
||||
message.pong.enrSeq == enrSeq
|
||||
message.pong.sprSeq == sprSeq
|
||||
message.pong.ip == ip
|
||||
message.pong.port == port
|
||||
|
||||
@ -56,7 +57,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(fn, reqId)
|
||||
check encoded.toHex == "03c501c3820100"
|
||||
check byteutils.toHex(encoded) == "03c501c3820100"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -74,7 +75,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(n, reqId)
|
||||
check encoded.toHex == "04c30101c0"
|
||||
check byteutils.toHex(encoded) == "04c30101c0"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -84,19 +85,19 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
message.reqId == reqId
|
||||
message.kind == nodes
|
||||
message.nodes.total == total
|
||||
message.nodes.enrs.len() == 0
|
||||
message.nodes.sprs.len() == 0
|
||||
|
||||
test "Nodes Response (multiple)":
|
||||
var e1, e2: Record
|
||||
check e1.fromURI("enr:-HW4QBzimRxkmT18hMKaAL3IcZF1UcfTMPyi3Q1pxwZZbcZVRI8DC5infUAB_UauARLOJtYTxaagKoGmIjzQxO2qUygBgmlkgnY0iXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTg")
|
||||
check e2.fromURI("enr:-HW4QNfxw543Ypf4HXKXdYxkyzfcxcO-6p9X986WldfVpnVTQX1xlTnWrktEWUbeTZnmgOuAY_KUhbVV1Ft98WoYUBMBgmlkgnY0iXNlY3AyNTZrMaEDDiy3QkHAxPyOgWbxp5oF1bDdlYE6dLCUUp8xfVw50jU")
|
||||
var s1, s2: SignedPeerRecord
|
||||
check s1.fromURI("spr:CiQIARIgWu2YZ5TQVW1gWEfvQijVHqSBtjCbwDt9VppJvYpHX9wSAgMBGlUKJgAkCAESIFrtmGeU0FVtYFhH70Io1R6kgbYwm8A7fVaaSb2KR1_cEKz1xZEGGgsKCQQAAAAAkQIAARoLCgkEAAAAAJECAAIaCwoJBAAAAACRAgADKkAjkK9DeWc82uzd1AEjRr-ksQyRiQ7vYGV4Af3FAEi0JgHvMC8RCQdqn2wBYxvBcyO8o1XMEEKCG01AUZrJlCkD")
|
||||
check s2.fromURI("spr:CiQIARIguW3cNKnlvRsJVmV0ddgFMmvfAQLi0zf4tlt_6WGA03YSAgMBGlUKJgAkCAESILlt3DSp5b0bCVZldHXYBTJr3wEC4tM3-LZbf-lhgNN2EKz1xZEGGgsKCQQAAAAAkQIAARoLCgkEAAAAAJECAAIaCwoJBAAAAACRAgADKkC4Y9NkDHf-71LOvZon0NjmyzQnkm4IlAJGMDPS0cbSgIF3-2cECC5mRiXHjcHWlI5hPpxUURxFyIgSp7XX1jIL")
|
||||
let
|
||||
total = 0x1'u32
|
||||
n = NodesMessage(total: total, enrs: @[e1, e2])
|
||||
n = NodesMessage(total: total, sprs: @[s1, s2])
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(n, reqId)
|
||||
check encoded.toHex == "04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
|
||||
check byteutils.toHex(encoded) == "04f9018f0101f9018ab8c30a24080112205aed986794d0556d605847ef4228d51ea481b6309bc03b7d569a49bd8a475fdc120203011a550a260024080112205aed986794d0556d605847ef4228d51ea481b6309bc03b7d569a49bd8a475fdc10acf5c591061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a402390af4379673cdaecddd4012346bfa4b10c91890eef60657801fdc50048b42601ef302f1109076a9f6c01631bc17323bca355cc1042821b4d40519ac9942903b8c30a2408011220b96ddc34a9e5bd1b0956657475d805326bdf0102e2d337f8b65b7fe96180d376120203011a550a26002408011220b96ddc34a9e5bd1b0956657475d805326bdf0102e2d337f8b65b7fe96180d37610acf5c591061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a40b863d3640c77feef52cebd9a27d0d8e6cb3427926e089402463033d2d1c6d2808177fb6704082e664625c78dc1d6948e613e9c54511c45c88812a7b5d7d6320b"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -106,9 +107,9 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
message.reqId == reqId
|
||||
message.kind == nodes
|
||||
message.nodes.total == total
|
||||
message.nodes.enrs.len() == 2
|
||||
message.nodes.enrs[0] == e1
|
||||
message.nodes.enrs[1] == e2
|
||||
message.nodes.sprs.len() == 2
|
||||
message.nodes.sprs[0] == s1
|
||||
message.nodes.sprs[1] == s2
|
||||
|
||||
test "Talk Request":
|
||||
let
|
||||
@ -116,7 +117,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(tr, reqId)
|
||||
check encoded.toHex == "05c901846563686f826869"
|
||||
check byteutils.toHex(encoded) == "05c901846563686f826869"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -134,7 +135,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
reqId = RequestId(id: @[1.byte])
|
||||
|
||||
let encoded = encodeMessage(tr, reqId)
|
||||
check encoded.toHex == "06c401826869"
|
||||
check byteutils.toHex(encoded) == "06c401826869"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isOk()
|
||||
@ -147,12 +148,12 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||
|
||||
test "Ping with too large RequestId":
|
||||
let
|
||||
enrSeq = 1'u64
|
||||
p = PingMessage(enrSeq: enrSeq)
|
||||
sprSeq = 1'u64
|
||||
p = PingMessage(sprSeq: sprSeq)
|
||||
# 1 byte too large
|
||||
reqId = RequestId(id: @[0.byte, 1, 2, 3, 4, 5, 6, 7, 8])
|
||||
let encoded = encodeMessage(p, reqId)
|
||||
check encoded.toHex == "01cb8900010203040506070801"
|
||||
check byteutils.toHex(encoded) == "01cb8900010203040506070801"
|
||||
|
||||
let decoded = decodeMessage(encoded)
|
||||
check decoded.isErr()
|
||||
@ -176,8 +177,8 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||
sharedSecret = "0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e"
|
||||
|
||||
let
|
||||
pub = PublicKey.fromHex(publicKey)[]
|
||||
priv = PrivateKey.fromHex(secretKey)[]
|
||||
pub = keys.PublicKey.fromHex(publicKey)[]
|
||||
priv = keys.PrivateKey.fromHex(secretKey)[]
|
||||
eph = ecdhRawFull(priv, pub)
|
||||
check:
|
||||
eph.data == hexToSeqByte(sharedSecret)
|
||||
@ -197,8 +198,8 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||
let secrets = deriveKeys(
|
||||
NodeId.fromHex(nodeIdA),
|
||||
NodeId.fromHex(nodeIdB),
|
||||
PrivateKey.fromHex(ephemeralKey)[],
|
||||
PublicKey.fromHex(destPubkey)[],
|
||||
keys.PrivateKey.fromHex(ephemeralKey)[],
|
||||
keys.PublicKey.fromHex(destPubkey)[],
|
||||
hexToSeqByte(challengeData))
|
||||
|
||||
check:
|
||||
@ -216,7 +217,7 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||
idSignature = "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6"
|
||||
|
||||
let
|
||||
privKey = PrivateKey.fromHex(staticKey)[]
|
||||
privKey = keys.PrivateKey.fromHex(staticKey)[]
|
||||
signature = createIdSignature(
|
||||
privKey,
|
||||
hexToSeqByte(challengeData),
|
||||
@ -254,18 +255,18 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
var
|
||||
codecA, codecB: Codec
|
||||
nodeA, nodeB: Node
|
||||
privKeyA, privKeyB: PrivateKey
|
||||
privKeyA, privKeyB: keys.PrivateKey
|
||||
|
||||
setup:
|
||||
privKeyA = PrivateKey.fromHex(nodeAKey)[] # sender -> encode
|
||||
privKeyB = PrivateKey.fromHex(nodeBKey)[] # receive -> decode
|
||||
privKeyA = keys.PrivateKey.fromHex(nodeAKey)[] # sender -> encode
|
||||
privKeyB = keys.PrivateKey.fromHex(nodeBKey)[] # receive -> decode
|
||||
|
||||
let
|
||||
enrRecA = enr.Record.init(1, privKeyA,
|
||||
enrRecA = SignedPeerRecord.init(1, privKeyA,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
|
||||
enrRecB = enr.Record.init(1, privKeyB,
|
||||
enrRecB = SignedPeerRecord.init(1, privKeyB,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
|
||||
@ -280,7 +281,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
const
|
||||
readKey = "0x00000000000000000000000000000000"
|
||||
pingReqId = "0x00000001"
|
||||
pingEnrSeq = 2'u64
|
||||
pingSprSeq = 2'u64
|
||||
|
||||
encodedPacket =
|
||||
"00000000000000000000000000000000088b3d4342774649325f313964a39e55" &
|
||||
@ -300,14 +301,14 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
decoded.get().messageOpt.isSome()
|
||||
decoded.get().messageOpt.get().reqId.id == hexToSeqByte(pingReqId)
|
||||
decoded.get().messageOpt.get().kind == ping
|
||||
decoded.get().messageOpt.get().ping.enrSeq == pingEnrSeq
|
||||
decoded.get().messageOpt.get().ping.sprSeq == pingSprSeq
|
||||
|
||||
test "Whoareyou Packet":
|
||||
const
|
||||
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
||||
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
||||
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
||||
whoareyouEnrSeq = 0
|
||||
whoareyouSprSeq = 0
|
||||
|
||||
encodedPacket =
|
||||
"00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad" &
|
||||
@ -321,7 +322,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
decoded.get().flag == Flag.Whoareyou
|
||||
decoded.get().whoareyou.requestNonce == hexToByteArray[gcmNonceSize](whoareyouRequestNonce)
|
||||
decoded.get().whoareyou.idNonce == hexToByteArray[idNonceSize](whoareyouIdNonce)
|
||||
decoded.get().whoareyou.recordSeq == whoareyouEnrSeq
|
||||
decoded.get().whoareyou.recordSeq == whoareyouSprSeq
|
||||
decoded.get().whoareyou.challengeData == hexToSeqByte(whoareyouChallengeData)
|
||||
|
||||
codecB.decodePacket(nodeA.address.get(),
|
||||
@ -330,14 +331,14 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
test "Ping Handshake Message Packet":
|
||||
const
|
||||
pingReqId = "0x00000001"
|
||||
pingEnrSeq = 1'u64
|
||||
pingSprSeq = 1'u64
|
||||
#
|
||||
# handshake inputs:
|
||||
#
|
||||
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001"
|
||||
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
||||
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
||||
whoareyouEnrSeq = 1'u64
|
||||
whoareyouSprSeq = 1'u64
|
||||
|
||||
encodedPacket =
|
||||
"00000000000000000000000000000000088b3d4342774649305f313964a39e55" &
|
||||
@ -352,7 +353,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
whoareyouData = WhoareyouData(
|
||||
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
||||
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
||||
recordSeq: whoareyouEnrSeq,
|
||||
recordSeq: whoareyouSprSeq,
|
||||
challengeData: hexToSeqByte(whoareyouChallengeData))
|
||||
pubkey = some(privKeyA.toPublicKey())
|
||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
||||
@ -367,44 +368,45 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
decoded.isOk()
|
||||
decoded.get().message.reqId.id == hexToSeqByte(pingReqId)
|
||||
decoded.get().message.kind == ping
|
||||
decoded.get().message.ping.enrSeq == pingEnrSeq
|
||||
decoded.get().message.ping.sprSeq == pingSprSeq
|
||||
decoded.get().node.isNone()
|
||||
|
||||
codecB.decodePacket(nodeA.address.get(),
|
||||
hexToSeqByte(encodedPacket & "00")).isErr()
|
||||
|
||||
test "Ping Handshake Message Packet with ENR":
|
||||
test "Ping Handshake Message Packet with SPR":
|
||||
const
|
||||
pingReqId = "0x00000001"
|
||||
pingEnrSeq = 1'u64
|
||||
pingSprSeq = 1'u64
|
||||
#
|
||||
# handshake inputs:
|
||||
#
|
||||
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
||||
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
||||
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
||||
whoareyouEnrSeq = 0'u64
|
||||
whoareyouSprSeq = 0'u64
|
||||
|
||||
encodedPacket =
|
||||
"00000000000000000000000000000000088b3d4342774649305f313964a39e55" &
|
||||
"ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3" &
|
||||
"4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856" &
|
||||
"2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2" &
|
||||
"1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1" &
|
||||
"f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6" &
|
||||
"cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1" &
|
||||
"2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a" &
|
||||
"80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e" &
|
||||
"4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394" &
|
||||
"71"
|
||||
"2746cce362989b5d7e2496490b25f952e9198c524b06c7e9e069c5f7c8d2c84b" &
|
||||
"943322ac741826023cb35086eee94baaf98f81217c3dbcb022afb1464555b144" &
|
||||
"69b49cb19fe1f3459b4bbb03a52fc588bcc69d7ff50842ee6c3fc3ffd58d425f" &
|
||||
"e8c7bec9777fcb15d9c9e37c4aa3b226274f6631526d6d2127f39e1daff277fd" &
|
||||
"e867a8222ae509922d9e94456f7cbde14c1788894708713789b28b307ac983c8" &
|
||||
"31ebc00113ded4011af2bfa06078c8f0a3401e8c034b3ae5506fb002a0355bf1" &
|
||||
"48b19022bae8b088a0c0bdc22dc3d5ce4a6c5ad700a3f8a82be214c2bef98afe" &
|
||||
"2dbf4ffaaf816602d470dcfe8184b1db8d873d8813984f86b6350ff5d00d466c" &
|
||||
"06de59f1797ad01a68bb9c07b9cb56e6989ab0e94d32c60e435a48aa7c89d602" &
|
||||
"3863bd1605a33f895903657fe72f79ded24b366486a1c02a893702ec7d299ea8" &
|
||||
"7afe0bb771fad244b8d4d0bd7bf4dc833a17c4db2f926eb7614788308a6f98af" &
|
||||
"9a0e20bd75af75175645058702122b15"
|
||||
|
||||
let
|
||||
whoareyouData = WhoareyouData(
|
||||
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
||||
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
||||
recordSeq: whoareyouEnrSeq,
|
||||
recordSeq: whoareyouSprSeq,
|
||||
challengeData: hexToSeqByte(whoareyouChallengeData))
|
||||
pubkey = none(PublicKey)
|
||||
pubkey = none(keys.PublicKey)
|
||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
||||
key = HandshakeKey(nodeId: nodeA.id, address: nodeA.address.get())
|
||||
|
||||
@ -417,14 +419,14 @@ suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||
decoded.isOk()
|
||||
decoded.get().message.reqId.id == hexToSeqByte(pingReqId)
|
||||
decoded.get().message.kind == ping
|
||||
decoded.get().message.ping.enrSeq == pingEnrSeq
|
||||
decoded.get().message.ping.sprSeq == pingSprSeq
|
||||
decoded.get().node.isSome()
|
||||
|
||||
codecB.decodePacket(nodeA.address.get(),
|
||||
hexToSeqByte(encodedPacket & "00")).isErr()
|
||||
|
||||
suite "Discovery v5.1 Additional Encode/Decode":
|
||||
var rng = newRng()
|
||||
var rng = keys.newRng()
|
||||
|
||||
test "Encryption/Decryption":
|
||||
let
|
||||
@ -465,7 +467,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
var nonce: AESGCMNonce
|
||||
brHmacDrbgGenerate(rng[], nonce)
|
||||
let
|
||||
privKey = PrivateKey.random(rng[])
|
||||
privKey = keys.PrivateKey.random(rng[])
|
||||
nodeId = privKey.toPublicKey().toNodeId()
|
||||
authdata = newSeq[byte](32)
|
||||
staticHeader = encodeStaticHeader(Flag.OrdinaryMessage, nonce,
|
||||
@ -484,18 +486,18 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
var
|
||||
codecA, codecB: Codec
|
||||
nodeA, nodeB: Node
|
||||
privKeyA, privKeyB: PrivateKey
|
||||
privKeyA, privKeyB: keys.PrivateKey
|
||||
|
||||
setup:
|
||||
privKeyA = PrivateKey.random(rng[]) # sender -> encode
|
||||
privKeyB = PrivateKey.random(rng[]) # receiver -> decode
|
||||
privKeyA = keys.PrivateKey.random(rng[]) # sender -> encode
|
||||
privKeyB = keys.PrivateKey.random(rng[]) # receiver -> decode
|
||||
|
||||
let
|
||||
enrRecA = enr.Record.init(1, privKeyA,
|
||||
enrRecA = SignedPeerRecord.init(1, privKeyA,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
|
||||
enrRecB = enr.Record.init(1, privKeyB,
|
||||
enrRecB = SignedPeerRecord.init(1, privKeyB,
|
||||
some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)),
|
||||
some(Port(9000))).expect("Properly intialized private key")
|
||||
|
||||
@ -506,7 +508,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
|
||||
test "Encode / Decode Ordinary Random Message Packet":
|
||||
let
|
||||
m = PingMessage(enrSeq: 0)
|
||||
m = PingMessage(sprSeq: 0)
|
||||
reqId = RequestId.init(rng[])
|
||||
message = encodeMessage(m, reqId)
|
||||
|
||||
@ -526,7 +528,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
let recordSeq = 0'u64
|
||||
|
||||
let data = encodeWhoareyouPacket(rng[], codecA, nodeB.id,
|
||||
nodeB.address.get(), requestNonce, recordSeq, none(PublicKey))
|
||||
nodeB.address.get(), requestNonce, recordSeq, none(keys.PublicKey))
|
||||
|
||||
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
||||
|
||||
@ -546,7 +548,7 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
brHmacDrbgGenerate(rng[], requestNonce)
|
||||
let
|
||||
recordSeq = 1'u64
|
||||
m = PingMessage(enrSeq: 0)
|
||||
m = PingMessage(sprSeq: 0)
|
||||
reqId = RequestId.init(rng[])
|
||||
message = encodeMessage(m, reqId)
|
||||
pubkey = some(privKeyA.toPublicKey())
|
||||
@ -569,18 +571,18 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
decoded.isOk()
|
||||
decoded.get().message.reqId == reqId
|
||||
decoded.get().message.kind == ping
|
||||
decoded.get().message.ping.enrSeq == 0
|
||||
decoded.get().message.ping.sprSeq == 0
|
||||
decoded.get().node.isNone()
|
||||
|
||||
test "Encode / Decode Handshake Message Packet with ENR":
|
||||
test "Encode / Decode Handshake Message Packet with SPR":
|
||||
var requestNonce: AESGCMNonce
|
||||
brHmacDrbgGenerate(rng[], requestNonce)
|
||||
let
|
||||
recordSeq = 0'u64
|
||||
m = PingMessage(enrSeq: 0)
|
||||
m = PingMessage(sprSeq: 0)
|
||||
reqId = RequestId.init(rng[])
|
||||
message = encodeMessage(m, reqId)
|
||||
pubkey = none(PublicKey)
|
||||
pubkey = none(keys.PublicKey)
|
||||
|
||||
# Encode/decode whoareyou packet to get the handshake stored and the
|
||||
# whoareyou data returned. It's either that or construct the header for the
|
||||
@ -600,13 +602,13 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
decoded.isOk()
|
||||
decoded.get().message.reqId == reqId
|
||||
decoded.get().message.kind == ping
|
||||
decoded.get().message.ping.enrSeq == 0
|
||||
decoded.get().message.ping.sprSeq == 0
|
||||
decoded.get().node.isSome()
|
||||
decoded.get().node.get().record.seqNum == 1
|
||||
|
||||
test "Encode / Decode Ordinary Message Packet":
|
||||
let
|
||||
m = PingMessage(enrSeq: 0)
|
||||
m = PingMessage(sprSeq: 0)
|
||||
reqId = RequestId.init(rng[])
|
||||
message = encodeMessage(m, reqId)
|
||||
|
||||
@ -628,5 +630,5 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||
decoded.get().messageOpt.isSome()
|
||||
decoded.get().messageOpt.get().reqId == reqId
|
||||
decoded.get().messageOpt.get().kind == ping
|
||||
decoded.get().messageOpt.get().ping.enrSeq == 0
|
||||
decoded.get().messageOpt.get().ping.sprSeq == 0
|
||||
decoded[].requestNonce == nonce
|
||||
|
Loading…
x
Reference in New Issue
Block a user