mirror of https://github.com/status-im/nim-eth.git
Overhaul of ENR implementation - part I (#707)
- Rework adding and updating of fields by having an insert call that gets used everywhere. Avoiding also duplicate keys. One side-effect of this is that ENR sequence number will always get updated on an update call, even if nothing changes. - Deprecate initRecord as it is only used in tests and is flawed - Assert when predefined keys go into the extra custom pairs. Any of the predefined keys are only to be passed now via specific parameters to make sure that the correct types are stored in ENR. - Clearify the Opt.none behaviour for Record.update - When setting ipv6, allow for tcp/udp port fields to be used default - General clean-up - Rework/clean-up completely the ENR tests.
This commit is contained in:
parent
26212c881b
commit
7f20d79945
|
@ -11,7 +11,7 @@
|
||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[strutils, macros, algorithm, net],
|
std/[strutils, sequtils, macros, algorithm, net],
|
||||||
nimcrypto/[keccak, utils],
|
nimcrypto/[keccak, utils],
|
||||||
stew/base64,
|
stew/base64,
|
||||||
results,
|
results,
|
||||||
|
@ -19,33 +19,17 @@ import
|
||||||
".."/../[rlp, keys],
|
".."/../[rlp, keys],
|
||||||
../../net/utils
|
../../net/utils
|
||||||
|
|
||||||
export options, results, keys
|
export results, rlp, keys
|
||||||
|
|
||||||
const
|
const
|
||||||
maxEnrSize = 300 ## Maximum size of an encoded node record, in bytes.
|
maxEnrSize = 300 ## Maximum size of an encoded node record, in bytes.
|
||||||
minRlpListLen = 4 ## Minimum node record RLP list has: signature, seqId,
|
minRlpListLen = 4 ## Minimum node record RLP list has: signature, seqId,
|
||||||
## "id" key and value.
|
## "id" key and value.
|
||||||
|
PreDefinedKeys = ["id", "secp256k1", "ip", "ip6", "tcp", "tcp6", "udp", "udp6"]
|
||||||
|
## Predefined keys in the ENR spec, these have specific constraints on the
|
||||||
|
## type of the associated value.
|
||||||
|
|
||||||
type
|
type
|
||||||
FieldPair* = (string, Field)
|
|
||||||
|
|
||||||
Record* = object
|
|
||||||
seqNum*: uint64
|
|
||||||
raw*: seq[byte] # RLP encoded record
|
|
||||||
pairs: seq[FieldPair] # sorted list of all key/value pairs
|
|
||||||
|
|
||||||
EnrUri* = distinct string
|
|
||||||
|
|
||||||
TypedRecord* = object
|
|
||||||
id*: string
|
|
||||||
secp256k1*: Opt[array[33, byte]]
|
|
||||||
ip*: Opt[array[4, byte]]
|
|
||||||
ip6*: Opt[array[16, byte]]
|
|
||||||
tcp*: Opt[int]
|
|
||||||
udp*: Opt[int]
|
|
||||||
tcp6*: Opt[int]
|
|
||||||
udp6*: Opt[int]
|
|
||||||
|
|
||||||
FieldKind = enum
|
FieldKind = enum
|
||||||
kString,
|
kString,
|
||||||
kNum,
|
kNum,
|
||||||
|
@ -64,6 +48,28 @@ type
|
||||||
listRaw: seq[byte] ## Differently from the other kinds, this is is stored
|
listRaw: seq[byte] ## Differently from the other kinds, this is is stored
|
||||||
## as raw (encoded) RLP data, and thus treated as such further on.
|
## as raw (encoded) RLP data, and thus treated as such further on.
|
||||||
|
|
||||||
|
FieldPair* = (string, Field)
|
||||||
|
|
||||||
|
Record* = object
|
||||||
|
seqNum*: uint64 ## ENR sequence number
|
||||||
|
pairs*: seq[FieldPair] ## List of all key:value pairs. List must have
|
||||||
|
## at least the id k:v pair and the secp256k1 k:v pair. The list of pairs
|
||||||
|
## must remain sorted and without duplicate keys. Use the insert func to
|
||||||
|
## ensure this.
|
||||||
|
raw*: seq[byte] ## RLP encoded record
|
||||||
|
|
||||||
|
EnrUri* = distinct string
|
||||||
|
|
||||||
|
TypedRecord* = object
|
||||||
|
id*: string
|
||||||
|
secp256k1*: Opt[array[33, byte]]
|
||||||
|
ip*: Opt[array[4, byte]]
|
||||||
|
ip6*: Opt[array[16, byte]]
|
||||||
|
tcp*: Opt[int]
|
||||||
|
udp*: Opt[int]
|
||||||
|
tcp6*: Opt[int]
|
||||||
|
udp6*: Opt[int]
|
||||||
|
|
||||||
EnrResult*[T] = Result[T, cstring]
|
EnrResult*[T] = Result[T, cstring]
|
||||||
|
|
||||||
template toField[T](v: T): Field =
|
template toField[T](v: T): Field =
|
||||||
|
@ -94,8 +100,45 @@ func `==`(a, b: Field): bool =
|
||||||
else:
|
else:
|
||||||
false
|
false
|
||||||
|
|
||||||
|
template toFieldPair*(key: string, value: auto): FieldPair =
|
||||||
|
(key, toField(value))
|
||||||
|
|
||||||
func cmp(a, b: FieldPair): int = cmp(a[0], b[0])
|
func cmp(a, b: FieldPair): int = cmp(a[0], b[0])
|
||||||
|
|
||||||
|
func hasPredefinedKey(pair: FieldPair): bool =
|
||||||
|
PreDefinedKeys.contains(pair[0])
|
||||||
|
|
||||||
|
func hasPredefinedKey(pairs: openArray[FieldPair]): bool =
|
||||||
|
for pair in pairs:
|
||||||
|
if hasPredefinedKey(pair):
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
func find(pairs: openArray[FieldPair], key: string): Opt[int] =
|
||||||
|
## Search for key in key:value pairs.
|
||||||
|
##
|
||||||
|
## Returns some(index of key) if key is found. Else returns none.
|
||||||
|
for i, (k, v) in pairs:
|
||||||
|
if k == key:
|
||||||
|
return Opt.some(i)
|
||||||
|
Opt.none(int)
|
||||||
|
|
||||||
|
func insert(pairs: var seq[FieldPair], item: FieldPair) =
|
||||||
|
## Insert item in key:value pairs.
|
||||||
|
##
|
||||||
|
## If a FieldPair with key is already present, the value is updated, otherwise
|
||||||
|
## the pair is inserted in the correct position to keep the pairs sorted.
|
||||||
|
let index = find(pairs, item[0])
|
||||||
|
if index.isSome():
|
||||||
|
pairs[index.get()] = item
|
||||||
|
else:
|
||||||
|
pairs.insert(item, pairs.lowerBound(item, cmp))
|
||||||
|
|
||||||
|
func insert(pairs: var seq[FieldPair], b: openArray[FieldPair]) =
|
||||||
|
## Insert all items in key:value pairs.
|
||||||
|
for item in b:
|
||||||
|
pairs.insert(item)
|
||||||
|
|
||||||
func makeEnrRaw(
|
func makeEnrRaw(
|
||||||
seqNum: uint64, pk: PrivateKey,
|
seqNum: uint64, pk: PrivateKey,
|
||||||
pairs: openArray[FieldPair]): EnrResult[seq[byte]] =
|
pairs: openArray[FieldPair]): EnrResult[seq[byte]] =
|
||||||
|
@ -112,15 +155,17 @@ func makeEnrRaw(
|
||||||
of kList: w.appendRawBytes(v.listRaw) # No encoding needs to happen
|
of kList: w.appendRawBytes(v.listRaw) # No encoding needs to happen
|
||||||
w.finish()
|
w.finish()
|
||||||
|
|
||||||
let toSign = block:
|
let content =
|
||||||
|
block:
|
||||||
var w = initRlpList(pairs.len * 2 + 1)
|
var w = initRlpList(pairs.len * 2 + 1)
|
||||||
w.append(seqNum, pairs)
|
w.append(seqNum, pairs)
|
||||||
|
|
||||||
let sig = signNR(pk, toSign)
|
let signature = signNR(pk, content)
|
||||||
|
|
||||||
var raw = block:
|
let raw =
|
||||||
|
block:
|
||||||
var w = initRlpList(pairs.len * 2 + 2)
|
var w = initRlpList(pairs.len * 2 + 2)
|
||||||
w.append(sig.toRaw())
|
w.append(signature.toRaw())
|
||||||
w.append(seqNum, pairs)
|
w.append(seqNum, pairs)
|
||||||
|
|
||||||
if raw.len > maxEnrSize:
|
if raw.len > maxEnrSize:
|
||||||
|
@ -129,7 +174,7 @@ func makeEnrRaw(
|
||||||
ok(raw)
|
ok(raw)
|
||||||
|
|
||||||
func makeEnrAux(
|
func makeEnrAux(
|
||||||
seqNum: uint64, pk: PrivateKey,
|
seqNum: uint64, id: string, pk: PrivateKey,
|
||||||
pairs: openArray[FieldPair]): EnrResult[Record] =
|
pairs: openArray[FieldPair]): EnrResult[Record] =
|
||||||
var record: Record
|
var record: Record
|
||||||
record.pairs = @pairs
|
record.pairs = @pairs
|
||||||
|
@ -137,76 +182,70 @@ func makeEnrAux(
|
||||||
|
|
||||||
let pubkey = pk.toPublicKey()
|
let pubkey = pk.toPublicKey()
|
||||||
|
|
||||||
record.pairs.add(("id", Field(kind: kString, str: "v4")))
|
record.pairs.insert(("id", Field(kind: kString, str: id)))
|
||||||
record.pairs.add(("secp256k1",
|
record.pairs.insert(("secp256k1",
|
||||||
Field(kind: kBytes, bytes: @(pubkey.toRawCompressed()))))
|
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)
|
record.raw = ? makeEnrRaw(seqNum, pk, record.pairs)
|
||||||
ok(record)
|
ok(record)
|
||||||
|
|
||||||
macro initRecord*(
|
macro initRecord*(
|
||||||
seqNum: uint64, pk: PrivateKey,
|
seqNum: uint64, pk: PrivateKey,
|
||||||
pairs: untyped{nkTableConstr}): untyped =
|
pairs: untyped{nkTableConstr}): untyped {.deprecated: "Please use Record.init instead".} =
|
||||||
## Initialize a `Record` with given sequence number, private key and k:v
|
## Initialize a `Record` with given sequence number, private key and k:v
|
||||||
## pairs.
|
## pairs.
|
||||||
##
|
##
|
||||||
## Can fail in case the record exceeds the `maxEnrSize`.
|
## Can fail in case the record exceeds the `maxEnrSize`.
|
||||||
|
# Note: Deprecated as it is flawed. It allows for any type to be stored in the
|
||||||
|
# predefined keys. It also allows for duplicate keys (which could be fixed)
|
||||||
|
# and no longer sorts the pairs. It can however be moved and used for testing
|
||||||
|
# purposes.
|
||||||
|
|
||||||
for c in pairs:
|
for c in pairs:
|
||||||
c.expectKind(nnkExprColonExpr)
|
c.expectKind(nnkExprColonExpr)
|
||||||
c[1] = newCall(bindSym"toField", c[1])
|
c[1] = newCall(bindSym"toField", c[1])
|
||||||
|
|
||||||
result = quote do:
|
result = quote do:
|
||||||
makeEnrAux(`seqNum`, `pk`, `pairs`)
|
makeEnrAux(`seqNum`, "v4", `pk`, `pairs`)
|
||||||
|
|
||||||
template toFieldPair*(key: string, value: auto): FieldPair =
|
func insertAddress(
|
||||||
(key, toField(value))
|
|
||||||
|
|
||||||
func addAddress(
|
|
||||||
fields: var seq[FieldPair],
|
fields: var seq[FieldPair],
|
||||||
ip: Opt[IpAddress],
|
ip: Opt[IpAddress],
|
||||||
tcpPort, udpPort: Opt[Port]) =
|
tcpPort, udpPort: Opt[Port]) =
|
||||||
## Add address information in new fields. Incomplete address
|
## Insert address data.
|
||||||
## information is allowed (example: Port but not IP) as that information
|
## Incomplete address information is allowed (example: Port but not IP) as
|
||||||
## might be already in the ENR or added later.
|
## that information might be already in the ENR or added later.
|
||||||
if ip.isSome():
|
if ip.isSome():
|
||||||
let
|
case ip.value.family
|
||||||
ipExt = ip.get()
|
of IPv4:
|
||||||
isV6 = ipExt.family == IPv6
|
fields.insert(("ip", ip.value.address_v4.toField))
|
||||||
|
of IPv6:
|
||||||
|
fields.insert(("ip6", ip.value.address_v6.toField))
|
||||||
|
|
||||||
fields.add(if isV6: ("ip6", ipExt.address_v6.toField)
|
|
||||||
else: ("ip", ipExt.address_v4.toField))
|
|
||||||
if tcpPort.isSome():
|
if tcpPort.isSome():
|
||||||
fields.add(((if isV6: "tcp6" else: "tcp"), tcpPort.get().uint16.toField))
|
fields.insert(("tcp", tcpPort.get().uint16.toField))
|
||||||
if udpPort.isSome():
|
if udpPort.isSome():
|
||||||
fields.add(((if isV6: "udp6" else: "udp"), udpPort.get().uint16.toField))
|
fields.insert(("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))
|
|
||||||
|
|
||||||
func init*(
|
func init*(
|
||||||
T: type Record,
|
T: type Record,
|
||||||
seqNum: uint64, pk: PrivateKey,
|
seqNum: uint64, pk: PrivateKey,
|
||||||
ip: Opt[IpAddress],
|
ip: Opt[IpAddress] = Opt.none(IpAddress),
|
||||||
tcpPort, udpPort: Opt[Port],
|
tcpPort: Opt[Port] = Opt.none(Port),
|
||||||
|
udpPort: Opt[Port] = Opt.none(Port),
|
||||||
extraFields: openArray[FieldPair] = []):
|
extraFields: openArray[FieldPair] = []):
|
||||||
EnrResult[T] =
|
EnrResult[T] =
|
||||||
## Initialize a `Record` with given sequence number, private key, optional
|
## Initialize a `Record` with given sequence number, private key, optional
|
||||||
## ip address, tcp port, udp port, and optional custom k:v pairs.
|
## ip address, tcp port, udp port, and optional custom k:v pairs.
|
||||||
##
|
##
|
||||||
## Can fail in case the record exceeds the `maxEnrSize`.
|
## Can fail in case the record exceeds the `maxEnrSize`.
|
||||||
|
doAssert(not hasPredefinedKey(extraFields), "Predefined key in custom pairs")
|
||||||
|
|
||||||
var fields = newSeq[FieldPair]()
|
var fields = newSeq[FieldPair]()
|
||||||
|
|
||||||
# TODO: Allow for initializing ENR with both ip4 and ipv6 address.
|
fields.insertAddress(ip, tcpPort, udpPort)
|
||||||
fields.addAddress(ip, tcpPort, udpPort)
|
fields.insert extraFields
|
||||||
fields.add extraFields
|
makeEnrAux(seqNum, "v4", pk, fields)
|
||||||
makeEnrAux(seqNum, pk, fields)
|
|
||||||
|
|
||||||
func getField(r: Record, name: string, field: var Field): bool =
|
func getField(r: Record, name: string, field: var Field): bool =
|
||||||
# It might be more correct to do binary search,
|
# It might be more correct to do binary search,
|
||||||
|
@ -270,61 +309,10 @@ func get*(r: Record, T: type PublicKey): Opt[T] =
|
||||||
return Opt.some(pk[])
|
return Opt.some(pk[])
|
||||||
Opt.none(T)
|
Opt.none(T)
|
||||||
|
|
||||||
func find(r: Record, key: string): Opt[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 Opt.some(i)
|
|
||||||
Opt.none(int)
|
|
||||||
|
|
||||||
func update*(
|
func update*(
|
||||||
record: var Record, pk: PrivateKey,
|
record: var Record,
|
||||||
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(type r.seqNum): # highly unlikely
|
|
||||||
return err("Maximum sequence number reached")
|
|
||||||
r.seqNum.inc()
|
|
||||||
r.raw = ? makeEnrRaw(r.seqNum, pk, r.pairs)
|
|
||||||
record = r
|
|
||||||
|
|
||||||
ok()
|
|
||||||
|
|
||||||
func update*(
|
|
||||||
r: var Record,
|
|
||||||
pk: PrivateKey,
|
pk: PrivateKey,
|
||||||
ip: Opt[IpAddress],
|
ip: Opt[IpAddress] = Opt.none(IpAddress),
|
||||||
tcpPort: Opt[Port] = Opt.none(Port),
|
tcpPort: Opt[Port] = Opt.none(Port),
|
||||||
udpPort: Opt[Port] = Opt.none(Port),
|
udpPort: Opt[Port] = Opt.none(Port),
|
||||||
extraFields: openArray[FieldPair] = []):
|
extraFields: openArray[FieldPair] = []):
|
||||||
|
@ -332,18 +320,35 @@ func update*(
|
||||||
## Update a `Record` with given ip address, tcp port, udp port and optional
|
## Update a `Record` with given ip address, tcp port, udp port and optional
|
||||||
## custom k:v pairs.
|
## custom k:v pairs.
|
||||||
##
|
##
|
||||||
## In case any of the k:v pairs is updated or added (new), the sequence number
|
## If none of the k:v pairs are changed, the sequence number of the `Record`
|
||||||
## of the `Record` will be incremented and a new signature will be applied.
|
## will still be incremented and a new signature will be applied.
|
||||||
|
##
|
||||||
|
## Providing an `Opt.none` for `ip`, `tcpPort` or `udpPort` will leave the
|
||||||
|
## corresponding field untouched.
|
||||||
##
|
##
|
||||||
## Can fail in case of wrong `PrivateKey`, if the size of the resulting record
|
## 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`
|
## exceeds `maxEnrSize` or if maximum sequence number is reached. The `Record`
|
||||||
## will not be altered in these cases.
|
## will not be altered in these cases.
|
||||||
var fields = newSeq[FieldPair]()
|
# TODO: deprecate this call and have individual functions for updating?
|
||||||
|
doAssert(not hasPredefinedKey(extraFields), "Predefined key in custom pairs")
|
||||||
|
|
||||||
# TODO: Make updating of both ipv4 and ipv6 address in ENR more convenient.
|
var r = record
|
||||||
fields.addAddress(ip, tcpPort, udpPort)
|
|
||||||
fields.add extraFields
|
let pubkey = r.get(PublicKey)
|
||||||
r.update(pk, fields)
|
if pubkey.isNone() or pubkey.get() != pk.toPublicKey():
|
||||||
|
return err("Public key does not correspond with given private key")
|
||||||
|
|
||||||
|
r.pairs.insertAddress(ip, tcpPort, udpPort)
|
||||||
|
r.pairs.insert extraFields
|
||||||
|
|
||||||
|
if r.seqNum == high(type r.seqNum): # highly unlikely
|
||||||
|
return err("Maximum sequence number reached")
|
||||||
|
r.seqNum.inc()
|
||||||
|
|
||||||
|
r.raw = ? makeEnrRaw(r.seqNum, pk, r.pairs)
|
||||||
|
record = r
|
||||||
|
|
||||||
|
ok()
|
||||||
|
|
||||||
func tryGet*(r: Record, key: string, T: type): Opt[T] =
|
func tryGet*(r: Record, key: string, T: type): Opt[T] =
|
||||||
## Get the value from the provided key.
|
## Get the value from the provided key.
|
||||||
|
@ -548,7 +553,7 @@ func read*(
|
||||||
rlp: var Rlp, T: type Record):
|
rlp: var Rlp, T: type Record):
|
||||||
T {.raises: [RlpError, ValueError].} =
|
T {.raises: [RlpError, ValueError].} =
|
||||||
var res: T
|
var res: T
|
||||||
if not rlp.hasData() or not res.fromBytes(rlp.rawData):
|
if not rlp.hasData() or not res.fromBytes(rlp.rawData()):
|
||||||
# TODO: This could also just be an invalid signature, would be cleaner to
|
# TODO: This could also just be an invalid signature, would be cleaner to
|
||||||
# split of RLP deserialisation errors from this.
|
# split of RLP deserialisation errors from this.
|
||||||
raise newException(ValueError, "Could not deserialize")
|
raise newException(ValueError, "Could not deserialize")
|
||||||
|
|
|
@ -253,7 +253,7 @@ func updateRecord*(
|
||||||
d: Protocol, enrFields: openArray[(string, seq[byte])]): DiscResult[void] =
|
d: Protocol, enrFields: openArray[(string, seq[byte])]): DiscResult[void] =
|
||||||
## Update the ENR of the local node with provided `enrFields` k:v pairs.
|
## Update the ENR of the local node with provided `enrFields` k:v pairs.
|
||||||
let fields = mapIt(enrFields, toFieldPair(it[0], it[1]))
|
let fields = mapIt(enrFields, toFieldPair(it[0], it[1]))
|
||||||
d.localNode.record.update(d.privateKey, fields)
|
d.localNode.record.update(d.privateKey, extraFields = fields)
|
||||||
# TODO: Would it make sense to actively ping ("broadcast") to all the peers
|
# 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?
|
# we stored a handshake with in order to get that ENR updated?
|
||||||
|
|
||||||
|
@ -992,6 +992,8 @@ proc newProtocol*(
|
||||||
var record: Record
|
var record: Record
|
||||||
if previousRecord.isSome():
|
if previousRecord.isSome():
|
||||||
record = previousRecord.get()
|
record = previousRecord.get()
|
||||||
|
# TODO: this is faulty in case the intent is to remove a field with
|
||||||
|
# opt.none
|
||||||
record.update(privKey, enrIp, enrTcpPort, enrUdpPort,
|
record.update(privKey, enrIp, enrTcpPort, enrUdpPort,
|
||||||
customEnrFields).expect("Record within size limits and correct key")
|
customEnrFields).expect("Record within size limits and correct key")
|
||||||
else:
|
else:
|
||||||
|
@ -1045,6 +1047,8 @@ proc newProtocol*(
|
||||||
record =
|
record =
|
||||||
if previousRecord.isSome():
|
if previousRecord.isSome():
|
||||||
var res = previousRecord.get()
|
var res = previousRecord.get()
|
||||||
|
# TODO: this is faulty in case the intent is to remove a field with
|
||||||
|
# opt.none
|
||||||
res.update(privKey, enrIp, enrTcpPort, enrUdpPort,
|
res.update(privKey, enrIp, enrTcpPort, enrUdpPort,
|
||||||
customEnrFields).expect("Record within size limits and correct key")
|
customEnrFields).expect("Record within size limits and correct key")
|
||||||
res
|
res
|
||||||
|
|
|
@ -437,9 +437,9 @@ suite "Discovery v5 Tests":
|
||||||
previousRecord = Opt.some(updatesNode.getRecord()))
|
previousRecord = Opt.some(updatesNode.getRecord()))
|
||||||
check:
|
check:
|
||||||
node.getRecord().seqNum == 1
|
node.getRecord().seqNum == 1
|
||||||
noUpdatesNode.getRecord().seqNum == 1
|
noUpdatesNode.getRecord().seqNum == 2
|
||||||
updatesNode.getRecord().seqNum == 2
|
updatesNode.getRecord().seqNum == 3
|
||||||
moreUpdatesNode.getRecord().seqNum == 3
|
moreUpdatesNode.getRecord().seqNum == 4
|
||||||
|
|
||||||
# Defect (for now?) on incorrect key use
|
# Defect (for now?) on incorrect key use
|
||||||
expect ResultDefect:
|
expect ResultDefect:
|
||||||
|
|
|
@ -8,85 +8,156 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[sequtils, net],
|
std/[sequtils, net],
|
||||||
|
stew/byteutils,
|
||||||
unittest2,
|
unittest2,
|
||||||
../../eth/p2p/discoveryv5/enr, ../../eth/[keys, rlp]
|
../../eth/p2p/discoveryv5/enr, ../../eth/[keys, rlp]
|
||||||
|
|
||||||
let rng = newRng()
|
let rng = newRng()
|
||||||
|
|
||||||
suite "ENR":
|
proc testRlpEncodingLoop*(r: enr.Record): bool =
|
||||||
test "Serialization":
|
|
||||||
var pk = PrivateKey.fromHex(
|
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8]})[]
|
|
||||||
check($r == """(123, id: "v4", ip: 5.6.7.8, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
|
||||||
let uri = r.toURI()
|
|
||||||
var r2: Record
|
|
||||||
let sigValid = r2.fromURI(uri)
|
|
||||||
check(sigValid)
|
|
||||||
check($r2 == $r)
|
|
||||||
check(r2.raw == r.raw)
|
|
||||||
|
|
||||||
test "RLP serialisation":
|
|
||||||
var pk = PrivateKey.fromHex(
|
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8]})[]
|
|
||||||
check($r == """(123, id: "v4", ip: 5.6.7.8, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
|
||||||
let encoded = rlp.encode(r)
|
let encoded = rlp.encode(r)
|
||||||
let decoded = rlp.decode(encoded, enr.Record)
|
let decoded = rlp.decode(encoded, enr.Record)
|
||||||
check($decoded == $r)
|
decoded == r
|
||||||
check(decoded.raw == r.raw)
|
|
||||||
|
|
||||||
test "RLP deserialisation without data":
|
suite "ENR test vector tests":
|
||||||
|
# Tests using the test vector from:
|
||||||
|
# https://github.com/ethereum/devp2p/blob/master/enr.md#test-vectors
|
||||||
|
const
|
||||||
|
uri = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"
|
||||||
|
pk = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
||||||
|
seqNum = 1
|
||||||
|
id = "v4"
|
||||||
|
ip = "7f000001"
|
||||||
|
secp256k1 = "03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138"
|
||||||
|
udp = 0x765f
|
||||||
|
|
||||||
|
test "Test vector full encode loop":
|
||||||
|
var r: Record
|
||||||
|
let valid = r.fromURI(uri)
|
||||||
|
check valid
|
||||||
|
let res = toTypedRecord(r)
|
||||||
|
check res.isOk()
|
||||||
|
let typedRecord = res.value
|
||||||
|
check:
|
||||||
|
r.seqNum == seqNum
|
||||||
|
typedRecord.id == id
|
||||||
|
typedRecord.ip.value() == array[4, byte].fromHex(ip)
|
||||||
|
typedRecord.secp256k1.value() == array[33, byte].fromHex(secp256k1)
|
||||||
|
typedRecord.udp.value() == udp
|
||||||
|
typedRecord.tcp.isNone()
|
||||||
|
|
||||||
|
$r == """(1, id: "v4", ip: 127.0.0.1, secp256k1: 0x03CA634CAE0D49ACB401D8A4C6B6FE8C55B70D115BF400769CC1400F3258CD3138, udp: 30303)"""
|
||||||
|
|
||||||
|
r.toURI() == uri
|
||||||
|
|
||||||
|
test "Test vector Record.init":
|
||||||
|
let privKey = PrivateKey.fromHex(
|
||||||
|
pk).expect("valid private key")
|
||||||
|
|
||||||
|
var r = Record.init(1, privKey,
|
||||||
|
Opt.some(IpAddress(family: IPv4, address_v4: array[4, byte].fromHex(ip))),
|
||||||
|
Opt.none(Port), Opt.some(Port(udp)))
|
||||||
|
|
||||||
|
check:
|
||||||
|
r.isOk()
|
||||||
|
r.value.seqNum == seqNum
|
||||||
|
r.value.toURI() == uri
|
||||||
|
|
||||||
|
suite "ENR encoding tests":
|
||||||
|
test "RLP serialisation":
|
||||||
|
let
|
||||||
|
keypair = KeyPair.random(rng[])
|
||||||
|
ip = parseIpAddress("1.2.3.4")
|
||||||
|
port = Opt.some(Port(9000))
|
||||||
|
enr = Record.init(
|
||||||
|
100, keypair.seckey, Opt.some(ip), port, port)
|
||||||
|
|
||||||
|
check:
|
||||||
|
enr.isOk()
|
||||||
|
testRlpEncodingLoop(enr.value)
|
||||||
|
|
||||||
|
test "Empty RLP":
|
||||||
expect ValueError:
|
expect ValueError:
|
||||||
let decoded = rlp.decode([], enr.Record)
|
let _ = rlp.decode([], enr.Record)
|
||||||
|
|
||||||
var r: Record
|
var r: Record
|
||||||
check not fromBytes(r, [])
|
check not fromBytes(r, [])
|
||||||
|
|
||||||
test "Base64 deserialisation without data":
|
test "Invalid RLP":
|
||||||
var r: Record
|
expect RlpError:
|
||||||
let sigValid = r.fromURI("enr:")
|
let _ = rlp.decode([byte 0xf7], enr.Record)
|
||||||
check(not sigValid)
|
|
||||||
|
|
||||||
test "Parsing":
|
|
||||||
var r: Record
|
var r: Record
|
||||||
let sigValid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
check not fromBytes(r, [byte 0xf7])
|
||||||
check(sigValid)
|
|
||||||
check($r == """(1, id: "v4", ip: 127.0.0.1, secp256k1: 0x03CA634CAE0D49ACB401D8A4C6B6FE8C55B70D115BF400769CC1400F3258CD3138, udp: 30303)""")
|
test "No RLP list":
|
||||||
|
expect ValueError:
|
||||||
|
let _ = rlp.decode([byte 0x7f], enr.Record)
|
||||||
|
|
||||||
test "Bad base64":
|
|
||||||
var r: Record
|
var r: Record
|
||||||
let sigValid = r.fromURI("enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnhMHcBFZntXNFrdv*jX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
check not fromBytes(r, [byte 0x7f])
|
||||||
check(not sigValid)
|
|
||||||
|
|
||||||
test "Bad rlp":
|
test "ENR with RLP list value":
|
||||||
var r: Record
|
type
|
||||||
let sigValid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOOnrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
RlpTestList = object
|
||||||
check(not sigValid)
|
number: uint16
|
||||||
|
data: seq[byte]
|
||||||
|
text: string
|
||||||
|
|
||||||
test "Create from ENode address":
|
|
||||||
let
|
let
|
||||||
keypair = KeyPair.random(rng[])
|
rlpList = RlpTestList(number: 72, data: @[byte 0x0, 0x1, 0x2], text: "Hi there")
|
||||||
ip = parseIpAddress("10.20.30.40")
|
pk = PrivateKey.fromHex(
|
||||||
port = Opt.some(Port(9000))
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d").expect("valid private key")
|
||||||
|
ip = parseIpAddress("5.6.7.8")
|
||||||
|
port = Opt.some(Port(1234))
|
||||||
|
customPairs = [toFieldPair("some_list", rlpList)]
|
||||||
enr = Record.init(
|
enr = Record.init(
|
||||||
100, keypair.seckey, Opt.some(ip), port, port,@[])[]
|
123, pk, Opt.some(ip), Opt.none(Port), port, customPairs)
|
||||||
typedEnr = get enr.toTypedRecord()
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
typedEnr.secp256k1.isSome()
|
enr.isOk()
|
||||||
typedEnr.secp256k1.get == keypair.pubkey.toRawCompressed()
|
$enr.value == """(123, id: "v4", ip: 5.6.7.8, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, some_list: (Raw RLP list) 0xCE4883000102884869207468657265, udp: 1234)"""
|
||||||
|
testRlpEncodingLoop(enr.value)
|
||||||
|
|
||||||
typedEnr.ip.isSome()
|
test "Base64 encode loop":
|
||||||
typedEnr.ip.get() == [byte 10, 20, 30, 40]
|
const encodedBase64 = "-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"
|
||||||
|
var r: Record
|
||||||
|
check:
|
||||||
|
r.fromBase64(encodedBase64)
|
||||||
|
toBase64(r) == encodedBase64
|
||||||
|
|
||||||
typedEnr.tcp.isSome()
|
test "Invalid base64":
|
||||||
typedEnr.tcp.get() == 9000
|
var r: Record
|
||||||
|
let valid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnhMHcBFZntXNFrdv*jX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
||||||
|
check not valid
|
||||||
|
|
||||||
typedEnr.udp.isSome()
|
test "URI encode loop":
|
||||||
typedEnr.udp.get() == 9000
|
let
|
||||||
|
keypair = KeyPair.random(rng[])
|
||||||
|
ip = parseIpAddress("1.2.3.4")
|
||||||
|
port = Opt.some(Port(9000))
|
||||||
|
res = Record.init(
|
||||||
|
100, keypair.seckey, Opt.some(ip), port, port)
|
||||||
|
check res.isOk()
|
||||||
|
let enr = res.value()
|
||||||
|
let uri = enr.toURI()
|
||||||
|
var enr2: Record
|
||||||
|
let valid = enr2.fromURI(uri)
|
||||||
|
check(valid)
|
||||||
|
check(enr == enr2)
|
||||||
|
|
||||||
test "ENR without address":
|
test "Invalid URI: empty":
|
||||||
|
var r: Record
|
||||||
|
let valid = r.fromURI("")
|
||||||
|
check not valid
|
||||||
|
|
||||||
|
test "Invalid URI: no payload":
|
||||||
|
var r: Record
|
||||||
|
let valid = r.fromURI("enr:")
|
||||||
|
check not valid
|
||||||
|
|
||||||
|
suite "ENR init tests":
|
||||||
|
test "Record.init minimum fields":
|
||||||
let
|
let
|
||||||
keypair = KeyPair.random(rng[])
|
keypair = KeyPair.random(rng[])
|
||||||
port = Opt.none(Port)
|
port = Opt.none(Port)
|
||||||
|
@ -95,6 +166,8 @@ suite "ENR":
|
||||||
typedEnr = get enr.toTypedRecord()
|
typedEnr = get enr.toTypedRecord()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
testRlpEncodingLoop(enr)
|
||||||
|
|
||||||
typedEnr.secp256k1.isSome()
|
typedEnr.secp256k1.isSome()
|
||||||
typedEnr.secp256k1.get() == keypair.pubkey.toRawCompressed()
|
typedEnr.secp256k1.get() == keypair.pubkey.toRawCompressed()
|
||||||
|
|
||||||
|
@ -106,17 +179,113 @@ suite "ENR":
|
||||||
typedEnr.tcp6.isNone()
|
typedEnr.tcp6.isNone()
|
||||||
typedEnr.udp6.isNone()
|
typedEnr.udp6.isNone()
|
||||||
|
|
||||||
test "ENR init size too big":
|
test "Record.init only ipv4":
|
||||||
let pk = PrivateKey.fromHex(
|
let
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
keypair = KeyPair.random(rng[])
|
||||||
|
ip = parseIpAddress("1.2.3.4")
|
||||||
|
port = Opt.some(Port(9000))
|
||||||
|
enr = Record.init(
|
||||||
|
100, keypair.seckey, Opt.some(ip), port, port)[]
|
||||||
|
typedEnr = get enr.toTypedRecord()
|
||||||
|
|
||||||
|
check:
|
||||||
|
typedEnr.ip.isSome()
|
||||||
|
typedEnr.ip.get() == [byte 1, 2, 3, 4]
|
||||||
|
|
||||||
|
typedEnr.tcp.isSome()
|
||||||
|
typedEnr.tcp.get() == 9000
|
||||||
|
|
||||||
|
typedEnr.udp.isSome()
|
||||||
|
typedEnr.udp.get() == 9000
|
||||||
|
|
||||||
|
test "Record.init only ipv6":
|
||||||
|
let
|
||||||
|
keypair = KeyPair.random(rng[])
|
||||||
|
ip = parseIpAddress("::1")
|
||||||
|
port = Opt.some(Port(9000))
|
||||||
|
enr = Record.init(
|
||||||
|
100, keypair.seckey, Opt.some(ip), port, port)[]
|
||||||
|
typedEnr = get enr.toTypedRecord()
|
||||||
|
|
||||||
|
check:
|
||||||
|
typedEnr.ip.isNone()
|
||||||
|
typedEnr.tcp.isSome()
|
||||||
|
typedEnr.tcp.value() == 9000
|
||||||
|
typedEnr.udp.isSome()
|
||||||
|
typedEnr.udp.value() == 9000
|
||||||
|
|
||||||
|
typedEnr.ip6.isSome()
|
||||||
|
typedEnr.ip6.get() == [byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
|
||||||
|
|
||||||
|
typedEnr.tcp6.isNone()
|
||||||
|
typedEnr.udp6.isNone()
|
||||||
|
|
||||||
|
test "Record.init max ENR size":
|
||||||
|
let
|
||||||
|
pk = PrivateKey.fromHex(
|
||||||
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d").expect("valid private key")
|
||||||
block: # This gives ENR of 300 bytes encoded
|
block: # This gives ENR of 300 bytes encoded
|
||||||
let r = initRecord(1, pk, {"maxvalue": repeat(byte 2, 169),})
|
let r = Record.init(
|
||||||
|
1, pk, extraFields = [toFieldPair("maxvalue", repeat(byte 2, 169))]
|
||||||
|
)
|
||||||
check r.isOk()
|
check r.isOk()
|
||||||
|
|
||||||
block: # This gives ENR of 301 bytes encoded
|
block: # This gives ENR of 301 bytes encoded
|
||||||
let r = initRecord(1, pk, {"maxplus1": repeat(byte 2, 170),})
|
let r = Record.init(
|
||||||
|
1, pk, extraFields = [toFieldPair("maxplus1", repeat(byte 2, 170))]
|
||||||
|
)
|
||||||
check r.isErr()
|
check r.isErr()
|
||||||
|
|
||||||
|
test "PreDefinedKeys in custom pairs":
|
||||||
|
let
|
||||||
|
keypair = KeyPair.random(rng[])
|
||||||
|
customPairs = [toFieldPair("ip", @[byte 1, 1, 1, 1])]
|
||||||
|
|
||||||
|
expect AssertionDefect:
|
||||||
|
let _ = Record.init(
|
||||||
|
1, keypair.seckey, extraFields = customPairs)
|
||||||
|
|
||||||
|
test "Duplicate key":
|
||||||
|
# With duplicate key, the last one should be used (insert)
|
||||||
|
let
|
||||||
|
keypair = KeyPair.random(rng[])
|
||||||
|
customPairs = [
|
||||||
|
toFieldPair("test1", @[byte 1, 1, 1, 1]),
|
||||||
|
toFieldPair("test2", "abc"),
|
||||||
|
toFieldPair("test1", "1.2.3.4")
|
||||||
|
]
|
||||||
|
|
||||||
|
let res = Record.init(
|
||||||
|
1, keypair.seckey, extraFields = customPairs)
|
||||||
|
|
||||||
|
check: res.isOk()
|
||||||
|
let
|
||||||
|
enr = res.value
|
||||||
|
test1Field = enr.get("test1", string)
|
||||||
|
test2Field = enr.get("test2", string)
|
||||||
|
check:
|
||||||
|
test1Field.isOk()
|
||||||
|
test2Field.isOk()
|
||||||
|
test1Field.value == "1.2.3.4"
|
||||||
|
test2Field.value == "abc"
|
||||||
|
|
||||||
|
test "Record.init sorted":
|
||||||
|
let
|
||||||
|
pk = PrivateKey.fromHex(
|
||||||
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d").expect("valid private key")
|
||||||
|
customPairs = [
|
||||||
|
toFieldPair("abc", 1234'u),
|
||||||
|
toFieldPair("z", [byte 0]),
|
||||||
|
toFieldPair("123", "abc"),
|
||||||
|
toFieldPair("a12", 1'u)
|
||||||
|
]
|
||||||
|
r = Record.init(123, pk, extraFields = customPairs)
|
||||||
|
|
||||||
|
check:
|
||||||
|
r.isOk()
|
||||||
|
$r.value == """(123, 123: "abc", a12: 1, abc: 1234, id: "v4", secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, z: 0x00)"""
|
||||||
|
|
||||||
|
suite "ENR update tests":
|
||||||
test "ENR update":
|
test "ENR update":
|
||||||
let
|
let
|
||||||
pk = PrivateKey.fromHex(
|
pk = PrivateKey.fromHex(
|
||||||
|
@ -125,67 +294,81 @@ suite "ENR":
|
||||||
var r = Record.init(1, pk, Opt.none(IpAddress), Opt.none(Port), Opt.none(Port))[]
|
var r = Record.init(1, pk, Opt.none(IpAddress), Opt.none(Port), Opt.none(Port))[]
|
||||||
|
|
||||||
block: # Insert new k:v pair, update of seqNum should occur.
|
block: # Insert new k:v pair, update of seqNum should occur.
|
||||||
let updated = r.update(pk, [newField])
|
let updated = r.update(pk, extraFields = [newField])
|
||||||
check updated.isOk()
|
check updated.isOk()
|
||||||
check:
|
check:
|
||||||
r.get("test", uint).get() == 123
|
r.get("test", uint).get() == 123
|
||||||
r.seqNum == 2
|
r.seqNum == 2
|
||||||
|
|
||||||
block: # Insert same k:v pair, no update of seqNum should occur.
|
block: # Insert same k:v pair, update of seqNum still occurs.
|
||||||
let updated = r.update(pk, [newField])
|
let updated = r.update(pk, extraFields = [newField])
|
||||||
check updated.isOk()
|
check updated.isOk()
|
||||||
check:
|
check:
|
||||||
r.get("test", uint).get() == 123
|
r.get("test", uint).get() == 123
|
||||||
r.seqNum == 2
|
r.seqNum == 3
|
||||||
|
|
||||||
block: # Insert k:v pair with changed value, update of seqNum should occur.
|
block: # Insert k:v pair with changed value, update of seqNum should occur.
|
||||||
let updatedField = toFieldPair("test", 1234'u)
|
let updatedField = toFieldPair("test", 1234'u)
|
||||||
let updated = r.update(pk, [updatedField])
|
let updated = r.update(pk, extraFields = [updatedField])
|
||||||
check updated.isOk()
|
check updated.isOk()
|
||||||
check:
|
check:
|
||||||
r.get("test", uint).get() == 1234
|
r.get("test", uint).get() == 1234
|
||||||
r.seqNum == 3
|
r.seqNum == 4
|
||||||
|
|
||||||
test "ENR update sorted":
|
test "ENR update sorted":
|
||||||
let pk = PrivateKey.fromHex(
|
let
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
pk = PrivateKey.fromHex(
|
||||||
var r = initRecord(123, pk, {"abc": 1234'u,
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d").expect("valid private key")
|
||||||
"z": [byte 0],
|
customPairs = [
|
||||||
"123": "abc",
|
toFieldPair("abc", 1234'u),
|
||||||
"a12": 1'u})[]
|
toFieldPair("z", [byte 0]),
|
||||||
|
toFieldPair("123", "abc"),
|
||||||
|
toFieldPair("a12", 1'u)
|
||||||
|
]
|
||||||
|
res = Record.init(123, pk, extraFields = customPairs)
|
||||||
|
|
||||||
|
check res.isOk()
|
||||||
|
var r = res.value
|
||||||
|
|
||||||
check $r == """(123, 123: "abc", a12: 1, abc: 1234, id: "v4", secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, z: 0x00)"""
|
check $r == """(123, 123: "abc", a12: 1, abc: 1234, id: "v4", secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, z: 0x00)"""
|
||||||
|
|
||||||
let newField = toFieldPair("test", 123'u)
|
let newField = toFieldPair("test", 123'u)
|
||||||
let newField2 = toFieldPair("zzz", 123'u)
|
let newField2 = toFieldPair("zzz", 123'u)
|
||||||
let updated = r.update(pk, [newField, newField2])
|
let updated = r.update(pk, extraFields = [newField, newField2])
|
||||||
check updated.isOk()
|
check updated.isOk()
|
||||||
check $r == """(124, 123: "abc", a12: 1, abc: 1234, id: "v4", secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, test: 123, z: 0x00, zzz: 123)"""
|
check $r == """(124, 123: "abc", a12: 1, abc: 1234, id: "v4", secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, test: 123, z: 0x00, zzz: 123)"""
|
||||||
|
|
||||||
test "ENR update size too big":
|
test "ENR update too large":
|
||||||
let pk = PrivateKey.fromHex(
|
let
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
pk = PrivateKey.fromHex(
|
||||||
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d").expect("valid private key")
|
||||||
|
customPairs = [toFieldPair("maxvalue", repeat(byte 2, 169))]
|
||||||
|
|
||||||
var r = initRecord(1, pk, {"maxvalue": repeat(byte 2, 169),})
|
res = Record.init(123, pk, extraFields = customPairs)
|
||||||
check r.isOk()
|
|
||||||
|
check res.isOk()
|
||||||
|
var r = res.value
|
||||||
|
|
||||||
let newField = toFieldPair("test", 123'u)
|
let newField = toFieldPair("test", 123'u)
|
||||||
let updated = r[].update(pk, [newField])
|
let updated = r.update(pk, extraFields = [newField])
|
||||||
check updated.isErr()
|
check updated.isErr()
|
||||||
|
|
||||||
test "ENR update invalid key":
|
test "ENR update with wrong private key":
|
||||||
let pk = PrivateKey.fromHex(
|
let
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
pk = PrivateKey.fromHex(
|
||||||
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d").expect("valid private key")
|
||||||
|
|
||||||
var r = initRecord(1, pk, {"abc": 1'u,})
|
res = Record.init(123, pk)
|
||||||
check r.isOk()
|
check res.isOk()
|
||||||
|
var r = res.value
|
||||||
|
|
||||||
let
|
let
|
||||||
wrongPk = PrivateKey.random(rng[])
|
wrongPk = PrivateKey.random(rng[])
|
||||||
newField = toFieldPair("test", 123'u)
|
newField = toFieldPair("test", 123'u)
|
||||||
updated = r[].update(wrongPk, [newField])
|
updated = r.update(wrongPk, extraFields = [newField])
|
||||||
check updated.isErr()
|
check updated.isErr()
|
||||||
|
|
||||||
test "ENR update address":
|
test "ENR update addresses":
|
||||||
let
|
let
|
||||||
pk = PrivateKey.fromHex(
|
pk = PrivateKey.fromHex(
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
||||||
|
@ -200,7 +383,7 @@ suite "ENR":
|
||||||
r.tryGet("ip", uint).isNone()
|
r.tryGet("ip", uint).isNone()
|
||||||
r.tryGet("tcp", uint).isSome()
|
r.tryGet("tcp", uint).isSome()
|
||||||
r.tryGet("udp", uint).isSome()
|
r.tryGet("udp", uint).isSome()
|
||||||
r.seqNum == 1
|
r.seqNum == 2
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let updated = r.update(pk, Opt.none(IpAddress),
|
let updated = r.update(pk, Opt.none(IpAddress),
|
||||||
|
@ -210,7 +393,7 @@ suite "ENR":
|
||||||
r.tryGet("ip", uint).isNone()
|
r.tryGet("ip", uint).isNone()
|
||||||
r.tryGet("tcp", uint).isSome()
|
r.tryGet("tcp", uint).isSome()
|
||||||
r.tryGet("udp", uint).isSome()
|
r.tryGet("udp", uint).isSome()
|
||||||
r.seqNum == 2
|
r.seqNum == 3
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let updated = r.update(pk, Opt.some(parseIpAddress("10.20.30.40")),
|
let updated = r.update(pk, Opt.some(parseIpAddress("10.20.30.40")),
|
||||||
|
@ -229,10 +412,10 @@ suite "ENR":
|
||||||
typedEnr.udp.isSome()
|
typedEnr.udp.isSome()
|
||||||
typedEnr.udp.get() == 9000
|
typedEnr.udp.get() == 9000
|
||||||
|
|
||||||
r.seqNum == 3
|
r.seqNum == 4
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let updated = r.update(pk, Opt.some(parseIpAddress("10.20.30.40")),
|
let updated = r.update(pk, Opt.some(parseIpAddress("1.2.3.4")),
|
||||||
Opt.some(Port(9001)), Opt.some(Port(9001)))
|
Opt.some(Port(9001)), Opt.some(Port(9001)))
|
||||||
check updated.isOk()
|
check updated.isOk()
|
||||||
|
|
||||||
|
@ -240,7 +423,7 @@ suite "ENR":
|
||||||
|
|
||||||
check:
|
check:
|
||||||
typedEnr.ip.isSome()
|
typedEnr.ip.isSome()
|
||||||
typedEnr.ip.get() == [byte 10, 20, 30, 40]
|
typedEnr.ip.get() == [byte 1, 2, 3, 4]
|
||||||
|
|
||||||
typedEnr.tcp.isSome()
|
typedEnr.tcp.isSome()
|
||||||
typedEnr.tcp.get() == 9001
|
typedEnr.tcp.get() == 9001
|
||||||
|
@ -248,71 +431,4 @@ suite "ENR":
|
||||||
typedEnr.udp.isSome()
|
typedEnr.udp.isSome()
|
||||||
typedEnr.udp.get() == 9001
|
typedEnr.udp.get() == 9001
|
||||||
|
|
||||||
r.seqNum == 4
|
r.seqNum == 5
|
||||||
|
|
||||||
test "ENR with RLP list value":
|
|
||||||
type
|
|
||||||
RlpTestList = object
|
|
||||||
number: uint16
|
|
||||||
data: seq[byte]
|
|
||||||
text: string
|
|
||||||
|
|
||||||
let rlpList =
|
|
||||||
RlpTestList(number: 72, data: @[byte 0x0, 0x1, 0x2], text: "Hi there")
|
|
||||||
|
|
||||||
let pk = PrivateKey.fromHex(
|
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8],
|
|
||||||
"some_list": rlpList})[]
|
|
||||||
|
|
||||||
check($r == """(123, id: "v4", ip: 5.6.7.8, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, some_list: (Raw RLP list) 0xCE4883000102884869207468657265, udp: 1234)""")
|
|
||||||
|
|
||||||
let encoded = rlp.encode(r)
|
|
||||||
let decoded = rlp.decode(encoded, enr.Record)
|
|
||||||
check($decoded == $r)
|
|
||||||
check(decoded.raw == r.raw)
|
|
||||||
|
|
||||||
test "ENR IP addresses ":
|
|
||||||
let pk = PrivateKey.fromHex(
|
|
||||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
|
||||||
block: # valid ipv4
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8]})[]
|
|
||||||
|
|
||||||
check($r == """(123, id: "v4", ip: 5.6.7.8, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
|
||||||
|
|
||||||
let encoded = rlp.encode(r)
|
|
||||||
let decoded = rlp.decode(encoded, enr.Record)
|
|
||||||
check($decoded == $r)
|
|
||||||
check(decoded.raw == r.raw)
|
|
||||||
|
|
||||||
block: # invalid ipv4
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7]})[]
|
|
||||||
|
|
||||||
check($r == """(123, id: "v4", ip: (Invalid) 0x050607, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
|
||||||
|
|
||||||
let encoded = rlp.encode(r)
|
|
||||||
let decoded = rlp.decode(encoded, enr.Record)
|
|
||||||
check($decoded == $r)
|
|
||||||
check(decoded.raw == r.raw)
|
|
||||||
|
|
||||||
block: # valid ipv4 + ipv6
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8],
|
|
||||||
"ip6": [byte 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6]})[]
|
|
||||||
|
|
||||||
check($r == """(123, id: "v4", ip: 5.6.7.8, ip6: 102::102:304:506, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
|
||||||
|
|
||||||
let encoded = rlp.encode(r)
|
|
||||||
let decoded = rlp.decode(encoded, enr.Record)
|
|
||||||
check($decoded == $r)
|
|
||||||
check(decoded.raw == r.raw)
|
|
||||||
|
|
||||||
block: # invalid ipv4 + ipv6
|
|
||||||
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8, 9],
|
|
||||||
"ip6": [byte 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5]})[]
|
|
||||||
|
|
||||||
check($r == """(123, id: "v4", ip: (Invalid) 0x0506070809, ip6: (Invalid) 0x010200000000000000000102030405, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
|
||||||
|
|
||||||
let encoded = rlp.encode(r)
|
|
||||||
let decoded = rlp.decode(encoded, enr.Record)
|
|
||||||
check($decoded == $r)
|
|
||||||
check(decoded.raw == r.raw)
|
|
||||||
|
|
Loading…
Reference in New Issue