mirror of https://github.com/waku-org/nwaku.git
feat(common): added extensible implementation of the enr typed record
This commit is contained in:
parent
179be681c4
commit
ac56e1dcdd
|
@ -10,7 +10,7 @@ import
|
|||
../v2/testlib/waku2
|
||||
|
||||
|
||||
suite "nim-eth ENR - builder":
|
||||
suite "nim-eth ENR - builder and typed record":
|
||||
|
||||
test "Non-supported private key (ECDSA)":
|
||||
## Given
|
||||
|
@ -34,12 +34,19 @@ suite "nim-eth ENR - builder":
|
|||
## Then
|
||||
check enrRes.isOk()
|
||||
|
||||
let record = enrRes.tryGet().toTypedRecord().get()
|
||||
let record = enrRes.tryGet().toTyped().get()
|
||||
|
||||
let id = record.id
|
||||
check:
|
||||
@(record.secp256k1.get()) == expectedPubKey
|
||||
id == some(RecordId.V4)
|
||||
|
||||
let publicKey = record.secp256k1
|
||||
check:
|
||||
publicKey.isSome()
|
||||
@(publicKey.get()) == expectedPubKey
|
||||
|
||||
|
||||
suite "nim-eth ENR - builder ext: IP address and TCP/UDP ports":
|
||||
suite "nim-eth ENR - Ext: IP address and TCP/UDP ports":
|
||||
|
||||
test "EIP-778 test vector":
|
||||
## Given
|
||||
|
@ -92,13 +99,13 @@ suite "nim-eth ENR - builder ext: IP address and TCP/UDP ports":
|
|||
## Then
|
||||
check enrRes.isOk()
|
||||
|
||||
let record = enrRes.tryGet().toTypedRecord().get()
|
||||
let record = enrRes.tryGet().toTyped().get()
|
||||
check:
|
||||
@(record.secp256k1.get()) == expectedPubKey
|
||||
record.ip == some(enrIpAddr.address_v4)
|
||||
record.tcp == some(enrTcpPort.int)
|
||||
record.udp == none(int)
|
||||
record.ip6 == none(array[0..15, byte])
|
||||
record.tcp == some(enrTcpPort.uint16)
|
||||
record.udp == none(uint16)
|
||||
record.ip6 == none(array[16, byte])
|
||||
|
||||
test "IPv6 and UDP port":
|
||||
let
|
||||
|
@ -122,12 +129,12 @@ suite "nim-eth ENR - builder ext: IP address and TCP/UDP ports":
|
|||
## Then
|
||||
check enrRes.isOk()
|
||||
|
||||
let record = enrRes.tryGet().toTypedRecord().get()
|
||||
let record = enrRes.tryGet().toTyped().get()
|
||||
check:
|
||||
@(record.secp256k1.get()) == expectedPubKey
|
||||
record.ip == none(array[0..3, byte])
|
||||
record.tcp == none(int)
|
||||
record.udp == none(int)
|
||||
record.ip == none(array[4, byte])
|
||||
record.tcp == none(uint16)
|
||||
record.udp == none(uint16)
|
||||
record.ip6 == some(enrIpAddr.address_v6)
|
||||
record.tcp6 == none(int)
|
||||
record.udp6 == some(enrUdpPort.int)
|
||||
record.tcp6 == none(uint16)
|
||||
record.udp6 == some(enrUdpPort.uint16)
|
||||
|
|
|
@ -179,7 +179,7 @@ suite "Waku ENR - Multiaddresses":
|
|||
some(enrTcpPort), some(enrUdpPort),
|
||||
none(CapabilitiesBitfield),
|
||||
multiaddrs)
|
||||
typedRecord = record.toTypedRecord.get()
|
||||
typedRecord = record.toTyped().get()
|
||||
|
||||
# Check EIP-778 ENR fields
|
||||
check:
|
||||
|
@ -218,8 +218,8 @@ suite "Waku ENR - Multiaddresses":
|
|||
let
|
||||
# Known values correspond to shared test vectors with other Waku implementations
|
||||
knownIp = ValidIpAddress.init("18.223.219.100")
|
||||
knownUdpPort = some(9000.int)
|
||||
knownTcpPort = none(int)
|
||||
knownUdpPort = some(9000.uint16)
|
||||
knownTcpPort = none(uint16)
|
||||
knownMultiaddrs = @[MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss")[],
|
||||
MultiAddress.init("/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss")[]]
|
||||
knownEnr = "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSH" &
|
||||
|
@ -233,7 +233,7 @@ suite "Waku ENR - Multiaddresses":
|
|||
check:
|
||||
enrRecord.fromURI(knownEnr)
|
||||
|
||||
let typedRecord = enrRecord.toTypedRecord.get()
|
||||
let typedRecord = enrRecord.toTyped().get()
|
||||
|
||||
# Check EIP-778 ENR fields
|
||||
check:
|
||||
|
|
|
@ -1,107 +1,13 @@
|
|||
## An extension wrapper around nim-eth's ENR module
|
||||
|
||||
when (NimMajor, NimMinor) < (1, 4):
|
||||
{.push raises: [Defect].}
|
||||
else:
|
||||
{.push raises: [].}
|
||||
|
||||
|
||||
import eth/p2p/discoveryv5/enr
|
||||
import
|
||||
std/options,
|
||||
stew/results,
|
||||
stew/shims/net,
|
||||
eth/keys as eth_keys,
|
||||
eth/p2p/discoveryv5/enr,
|
||||
libp2p/crypto/crypto as libp2p_crypto
|
||||
|
||||
export enr
|
||||
|
||||
|
||||
## Builder
|
||||
|
||||
type EnrBuilder* = object
|
||||
seqNumber: uint64
|
||||
privateKey: eth_keys.PrivateKey
|
||||
fields: seq[FieldPair]
|
||||
|
||||
|
||||
proc init*(T: type EnrBuilder, key: eth_keys.PrivateKey, seqNum: uint64 = 1): EnrBuilder =
|
||||
EnrBuilder(
|
||||
seqNumber: seqNum,
|
||||
privateKey: key,
|
||||
fields: newSeq[FieldPair]()
|
||||
)
|
||||
|
||||
proc init*(T: type EnrBuilder, key: libp2p_crypto.PrivateKey, seqNum: uint64 = 1): EnrBuilder =
|
||||
# TODO: Inconvenient runtime assertion. Move this assertion to compile time
|
||||
if key.scheme != PKScheme.Secp256k1:
|
||||
raise newException(Defect, "invalid private key scheme")
|
||||
|
||||
let
|
||||
bytes = key.getRawBytes().expect("Private key is valid")
|
||||
privateKey = eth_keys.PrivateKey.fromRaw(bytes).expect("Raw private key is of valid length")
|
||||
|
||||
EnrBuilder.init(key=privateKey, seqNum=seqNum)
|
||||
|
||||
proc addFieldPair*(builder: var EnrBuilder, pair: FieldPair) =
|
||||
builder.fields.add(pair)
|
||||
|
||||
proc addFieldPair*[V](builder: var EnrBuilder, key: string, value: V) =
|
||||
builder.addFieldPair(toFieldPair(key, value))
|
||||
|
||||
proc build*(builder: EnrBuilder): EnrResult[enr.Record] =
|
||||
# Note that nim-eth's `Record.init` does not deduplicate the field pairs.
|
||||
# See: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L143-L144
|
||||
enr.Record.init(
|
||||
seqNum = builder.seqNumber,
|
||||
pk = builder.privateKey,
|
||||
ip = none(ValidIpAddress),
|
||||
tcpPort = none(Port),
|
||||
udpPort = none(Port),
|
||||
extraFields = builder.fields
|
||||
)
|
||||
|
||||
|
||||
## Builder extension: IP address and TCP/UDP ports
|
||||
|
||||
proc addAddressAndPorts(builder: var EnrBuilder, ip: ValidIpAddress, tcpPort, udpPort: Option[Port]) =
|
||||
# Based on: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L166
|
||||
let isV6 = ip.family == IPv6
|
||||
|
||||
let ipField = if isV6: toFieldPair("ip6", ip.address_v6)
|
||||
else: toFieldPair("ip", ip.address_v4)
|
||||
builder.addFieldPair(ipField)
|
||||
|
||||
if tcpPort.isSome():
|
||||
let
|
||||
tcpPortFieldKey = if isV6: "tcp6" else: "tcp"
|
||||
tcpPortFieldValue = tcpPort.get()
|
||||
builder.addFieldPair(tcpPortFieldKey, tcpPortFieldValue.uint16)
|
||||
|
||||
if udpPort.isSome():
|
||||
let
|
||||
udpPortFieldKey = if isV6: "udp6" else: "udp"
|
||||
udpPortFieldValue = udpPort.get()
|
||||
builder.addFieldPair(udpPortFieldKey, udpPortFieldValue.uint16)
|
||||
|
||||
proc addPorts(builder: var EnrBuilder, tcp, udp: Option[Port]) =
|
||||
# Based on: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L166
|
||||
|
||||
if tcp.isSome():
|
||||
let tcpPort = tcp.get()
|
||||
builder.addFieldPair("tcp", tcpPort.uint16)
|
||||
|
||||
if udp.isSome():
|
||||
let udpPort = udp.get()
|
||||
builder.addFieldPair("udp", udpPort.uint16)
|
||||
|
||||
|
||||
proc withIpAddressAndPorts*(builder: var EnrBuilder,
|
||||
ipAddr = none(ValidIpAddress),
|
||||
tcpPort = none(Port),
|
||||
udpPort = none(Port)) =
|
||||
if ipAddr.isSome():
|
||||
addAddressAndPorts(builder, ipAddr.get(), tcpPort, udpPort)
|
||||
else:
|
||||
addPorts(builder, tcpPort, udpPort)
|
||||
./enr/builder,
|
||||
./enr/typed_record
|
||||
|
||||
export
|
||||
enr.Record, enr.EnrResult, enr.get, enr.tryGet,
|
||||
enr.fromBase64, enr.toBase64, enr.fromURI, enr.toURI,
|
||||
enr.FieldPair, enr.toFieldPair, enr.init, # TODO: Delete after removing the deprecated procs
|
||||
builder,
|
||||
typed_record
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
when (NimMajor, NimMinor) < (1, 4):
|
||||
{.push raises: [Defect].}
|
||||
else:
|
||||
{.push raises: [].}
|
||||
|
||||
|
||||
import
|
||||
std/options,
|
||||
stew/results,
|
||||
stew/shims/net,
|
||||
eth/keys as eth_keys,
|
||||
eth/p2p/discoveryv5/enr,
|
||||
libp2p/crypto/crypto as libp2p_crypto
|
||||
|
||||
|
||||
## Builder
|
||||
|
||||
type EnrBuilder* = object
|
||||
seqNumber: uint64
|
||||
privateKey: eth_keys.PrivateKey
|
||||
fields: seq[FieldPair]
|
||||
|
||||
|
||||
proc init*(T: type EnrBuilder, key: eth_keys.PrivateKey, seqNum: uint64 = 1): T =
|
||||
EnrBuilder(
|
||||
seqNumber: seqNum,
|
||||
privateKey: key,
|
||||
fields: newSeq[FieldPair]()
|
||||
)
|
||||
|
||||
proc init*(T: type EnrBuilder, key: libp2p_crypto.PrivateKey, seqNum: uint64 = 1): T =
|
||||
# TODO: Inconvenient runtime assertion. Move this assertion to compile time
|
||||
if key.scheme != PKScheme.Secp256k1:
|
||||
raise newException(Defect, "invalid private key scheme")
|
||||
|
||||
let
|
||||
bytes = key.getRawBytes().expect("Private key is valid")
|
||||
privateKey = eth_keys.PrivateKey.fromRaw(bytes).expect("Raw private key is of valid length")
|
||||
|
||||
EnrBuilder.init(key=privateKey, seqNum=seqNum)
|
||||
|
||||
proc addFieldPair*(builder: var EnrBuilder, pair: FieldPair) =
|
||||
builder.fields.add(pair)
|
||||
|
||||
proc addFieldPair*[V](builder: var EnrBuilder, key: string, value: V) =
|
||||
builder.addFieldPair(toFieldPair(key, value))
|
||||
|
||||
proc build*(builder: EnrBuilder): EnrResult[enr.Record] =
|
||||
# Note that nim-eth's `Record.init` does not deduplicate the field pairs.
|
||||
# See: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L143-L144
|
||||
enr.Record.init(
|
||||
seqNum = builder.seqNumber,
|
||||
pk = builder.privateKey,
|
||||
ip = none(ValidIpAddress),
|
||||
tcpPort = none(Port),
|
||||
udpPort = none(Port),
|
||||
extraFields = builder.fields
|
||||
)
|
||||
|
||||
|
||||
## Builder extension: IP address and TCP/UDP ports
|
||||
|
||||
proc addAddressAndPorts(builder: var EnrBuilder, ip: ValidIpAddress, tcpPort, udpPort: Option[Port]) =
|
||||
# Based on: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L166
|
||||
let isV6 = ip.family == IPv6
|
||||
|
||||
let ipField = if isV6: toFieldPair("ip6", ip.address_v6)
|
||||
else: toFieldPair("ip", ip.address_v4)
|
||||
builder.addFieldPair(ipField)
|
||||
|
||||
if tcpPort.isSome():
|
||||
let
|
||||
tcpPortFieldKey = if isV6: "tcp6" else: "tcp"
|
||||
tcpPortFieldValue = tcpPort.get()
|
||||
builder.addFieldPair(tcpPortFieldKey, tcpPortFieldValue.uint16)
|
||||
|
||||
if udpPort.isSome():
|
||||
let
|
||||
udpPortFieldKey = if isV6: "udp6" else: "udp"
|
||||
udpPortFieldValue = udpPort.get()
|
||||
builder.addFieldPair(udpPortFieldKey, udpPortFieldValue.uint16)
|
||||
|
||||
proc addPorts(builder: var EnrBuilder, tcp, udp: Option[Port]) =
|
||||
# Based on: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L166
|
||||
|
||||
if tcp.isSome():
|
||||
let tcpPort = tcp.get()
|
||||
builder.addFieldPair("tcp", tcpPort.uint16)
|
||||
|
||||
if udp.isSome():
|
||||
let udpPort = udp.get()
|
||||
builder.addFieldPair("udp", udpPort.uint16)
|
||||
|
||||
|
||||
proc withIpAddressAndPorts*(builder: var EnrBuilder,
|
||||
ipAddr = none(ValidIpAddress),
|
||||
tcpPort = none(Port),
|
||||
udpPort = none(Port)) =
|
||||
if ipAddr.isSome():
|
||||
addAddressAndPorts(builder, ipAddr.get(), tcpPort, udpPort)
|
||||
else:
|
||||
addPorts(builder, tcpPort, udpPort)
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
when (NimMajor, NimMinor) < (1, 4):
|
||||
{.push raises: [Defect].}
|
||||
else:
|
||||
{.push raises: [].}
|
||||
|
||||
|
||||
import
|
||||
std/options,
|
||||
stew/results,
|
||||
eth/keys as eth_keys,
|
||||
libp2p/crypto/crypto as libp2p_crypto
|
||||
|
||||
import eth/p2p/discoveryv5/enr except TypedRecord, toTypedRecord
|
||||
|
||||
|
||||
## ENR typed record
|
||||
|
||||
# Record identity scheme
|
||||
|
||||
type RecordId* {.pure.} = enum
|
||||
V4
|
||||
|
||||
func toRecordId(id: string): EnrResult[RecordId] =
|
||||
case id:
|
||||
of "v4":
|
||||
ok(RecordId.V4)
|
||||
else:
|
||||
err("unknown identity scheme")
|
||||
|
||||
func `$`*(id: RecordId): string =
|
||||
case id:
|
||||
of RecordId.V4: "v4"
|
||||
|
||||
|
||||
# Typed record
|
||||
|
||||
type TypedRecord* = object
|
||||
raw: Record
|
||||
|
||||
proc init(T: type TypedRecord, record: Record): T =
|
||||
TypedRecord(raw: record)
|
||||
|
||||
proc tryGet*(record: TypedRecord, field: string, T: type): Option[T] =
|
||||
record.raw.tryGet(field, T)
|
||||
|
||||
func toTyped*(record: Record): EnrResult[TypedRecord] =
|
||||
let tr = TypedRecord.init(record)
|
||||
|
||||
# Validate record's identity scheme
|
||||
let idOpt = tr.tryGet("id", string)
|
||||
if idOpt.isNone():
|
||||
return err("missing id scheme field")
|
||||
|
||||
discard ? toRecordId(idOpt.get())
|
||||
|
||||
ok(tr)
|
||||
|
||||
|
||||
# Typed record field accessors
|
||||
|
||||
func id*(record: TypedRecord): Option[RecordId] =
|
||||
let fieldOpt = record.tryGet("id", string)
|
||||
if fieldOpt.isNone():
|
||||
return none(RecordId)
|
||||
|
||||
let fieldRes = toRecordId(fieldOpt.get())
|
||||
if fieldRes.isErr():
|
||||
return none(RecordId)
|
||||
|
||||
some(fieldRes.value)
|
||||
|
||||
func secp256k1*(record: TypedRecord): Option[array[33, byte]] =
|
||||
record.tryGet("secp256k1", array[33, byte])
|
||||
|
||||
func ip*(record: TypedRecord): Option[array[4, byte]] =
|
||||
record.tryGet("ip", array[4, byte])
|
||||
|
||||
func ip6*(record: TypedRecord): Option[array[16, byte]] =
|
||||
record.tryGet("ip6", array[16, byte])
|
||||
|
||||
func tcp*(record: TypedRecord): Option[uint16] =
|
||||
record.tryGet("tcp", uint16)
|
||||
|
||||
func tcp6*(record: TypedRecord): Option[uint16] =
|
||||
record.tryGet("tcp6", uint16)
|
||||
|
||||
func udp*(record: TypedRecord): Option[uint16] =
|
||||
record.tryGet("udp", uint16)
|
||||
|
||||
func udp6*(record: TypedRecord): Option[uint16] =
|
||||
record.tryGet("udp6", uint16)
|
|
@ -8,7 +8,7 @@ else:
|
|||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[bitops, sequtils],
|
||||
std/[options, bitops, sequtils],
|
||||
stew/[endians2, results],
|
||||
stew/shims/net,
|
||||
eth/keys,
|
||||
|
|
Loading…
Reference in New Issue