refactor(enr): node capabilities code clean up and reorganization

This commit is contained in:
Lorenzo Delgado 2023-03-08 15:44:10 +01:00 committed by GitHub
parent 67db35e29d
commit 7639d8d273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 221 additions and 174 deletions

View File

@ -269,10 +269,12 @@ proc initNode(conf: WakuNodeConf,
else:
@[]
wakuFlags = initWakuFlags(conf.lightpush,
conf.filter,
conf.store,
conf.relay)
wakuFlags = CapabilitiesBitfield.init(
lightpush = conf.lightpush,
filter = conf.filter,
store = conf.store,
relay = conf.relay
)
var node: WakuNode

View File

@ -36,7 +36,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
ip = ValidIpAddress.init("0.0.0.0")
node = WakuNode.new(nodeKey, ip, Port(wakuPort))
flags = initWakuFlags(lightpush = false, filter = false, store = false, relay = true)
flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true)
# assumes behind a firewall, so not care about being discoverable
node.wakuDiscv5 = WakuDiscoveryV5.new(

View File

@ -32,7 +32,7 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
ip = ValidIpAddress.init("0.0.0.0")
node = WakuNode.new(nodeKey, ip, Port(wakuPort))
flags = initWakuFlags(lightpush = false, filter = false, store = false, relay = true)
flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true)
# assumes behind a firewall, so not care about being discoverable
node.wakuDiscv5 = WakuDiscoveryV5.new(

View File

@ -38,10 +38,12 @@ procSuite "Waku Discovery v5":
nodeUdpPort3 = Port(9004)
node3 = WakuNode.new(nodeKey3, bindIp, nodeTcpPort3)
flags = initWakuFlags(lightpush = false,
filter = false,
store = false,
relay = true)
flags = CapabilitiesBitfield.init(
lightpush = false,
filter = false,
store = false,
relay = true
)
# E2E relay test paramaters
pubSubTopic = "/waku/2/default-waku/proto"
@ -128,10 +130,12 @@ procSuite "Waku Discovery v5":
extIp = ValidIpAddress.init("127.0.0.1")
expectedMultiAddr = MultiAddress.init("/ip4/200.200.200.200/tcp/9000/wss").tryGet()
flags = initWakuFlags(lightpush = false,
filter = false,
store = false,
relay = true)
flags = CapabilitiesBitfield.init(
lightpush = false,
filter = false,
store = false,
relay = true
)
nodeTcpPort1 = Port(9010)
nodeUdpPort1 = Port(9012)

View File

@ -1,14 +1,113 @@
{.used.}
import
std/[options, sequtils],
stew/byteutils,
std/options,
stew/[results, byteutils],
testutils/unittests
import
../../waku/v2/protocol/waku_enr,
./testlib/waku2
suite "Waku ENR":
suite "Waku ENR - Capabilities bitfield":
test "check capabilities support":
## Given
let bitfield: CapabilitiesBitfield = 0b0000_1101u8 # Lightpush, Filter, Relay
## Then
check:
bitfield.supportsCapability(Capabilities.Relay)
not bitfield.supportsCapability(Capabilities.Store)
bitfield.supportsCapability(Capabilities.Filter)
bitfield.supportsCapability(Capabilities.Lightpush)
test "bitfield to capabilities list":
## Given
let bitfield = CapabilitiesBitfield.init(
relay = true,
store = false,
lightpush = true,
filter = true
)
## When
let caps = bitfield.toCapabilities()
## Then
check:
caps == @[Capabilities.Relay, Capabilities.Filter, Capabilities.Lightpush]
test "encode and extract capabilities from record":
## Given
let enrkey = generatesecp256k1key()
let caps = CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store)
let record = Record.init(1, enrkey, wakuFlags=some(caps))
## When
let bitfieldRes = record.getCapabilitiesField()
## Then
check bitfieldRes.isOk()
let bitfield = bitfieldRes.tryGet()
check:
bitfield.toCapabilities() == @[Capabilities.Relay, Capabilities.Store]
test "cannot extract capabilities from record":
## Given
let enrkey = generatesecp256k1key()
let record = Record.init(1, enrkey, wakuFlags=none(CapabilitiesBitfield))
## When
let bitfieldRes = record.getCapabilitiesField()
## Then
check bitfieldRes.isErr()
let err = bitfieldRes.tryError()
check:
err == "Key not found in ENR"
test "check capabilities on a waku node record":
## Given
let wakuRecord = "-Hy4QC73_E3B_FkZhsOakaD4pHe-U--UoGASdG9N0F3SFFUDY_jdQbud8" &
"EXVyrlOZ5pZ7VYFBDPMRCENwy87Lh74dFIBgmlkgnY0iXNlY3AyNTZrMaECvNt1jIWbWGp" &
"AWWdlLGYm1E1OjlkQk3ONoxDC5sfw8oOFd2FrdTID"
## When
var record: Record
require waku_enr.fromBase64(record, wakuRecord)
## Then
check:
record.supportsCapability(Relay) == true
record.supportsCapability(Store) == true
record.supportsCapability(Filter) == false
record.supportsCapability(Lightpush) == false
record.getCapabilities() == @[Capabilities.Relay, Capabilities.Store]
test "check capabilities on a non-waku node record":
## Given
# non waku enr, i.e. Ethereum one
let nonWakuEnr = "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2G" &
"xb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNl" &
"Y3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA"
## When
var nonWakuEnrRecord: Record
require waku_enr.fromURI(nonWakuEnrRecord, nonWakuEnr)
## Then
check:
nonWakuEnrRecord.getCapabilities() == []
nonWakuEnrRecord.supportsCapability(Relay) == false
nonWakuEnrRecord.supportsCapability(Store) == false
nonWakuEnrRecord.supportsCapability(Filter) == false
nonWakuEnrRecord.supportsCapability(Lightpush) == false
suite "Waku ENR - Multiaddresses":
test "Parse multiaddr field":
let
@ -45,14 +144,13 @@ suite "Waku ENR":
enrIp = ValidIpAddress.init("127.0.0.1")
enrTcpPort, enrUdpPort = Port(61101)
enrKey = generateSecp256k1Key()
wakuFlags = initWakuFlags(false, true, false, true)
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[],
MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]]
let
record = enr.Record.init(enrKey, some(enrIp),
record = enr.Record.init(1, enrKey, some(enrIp),
some(enrTcpPort), some(enrUdpPort),
some(wakuFlags),
none(CapabilitiesBitfield),
multiaddrs)
typedRecord = record.toTypedRecord.get()
@ -64,11 +162,8 @@ suite "Waku ENR":
Port(typedRecord.udp.get()) == enrUdpPort
# Check Waku ENR fields
let
decodedFlags = record.get(WAKU_ENR_FIELD, seq[byte])[]
decodedAddrs = record.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
let decodedAddrs = record.get(MultiaddrEnrField, seq[byte]).tryGet().toMultiAddresses()
check:
decodedFlags == @[wakuFlags.byte]
decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[])
decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[])
@ -81,9 +176,9 @@ suite "Waku ENR":
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD")[]]
let
record = enr.Record.init(enrKey, some(enrIp),
record = enr.Record.init(1, enrKey, some(enrIp),
some(enrTcpPort), some(enrUdpPort),
none(WakuEnrBitfield),
none(CapabilitiesBitfield),
multiaddrs)
# Check Waku ENR fields
@ -126,92 +221,3 @@ suite "Waku ENR":
for knownMultiaddr in knownMultiaddrs:
check decodedAddrs.contains(knownMultiaddr)
test "Supports specific capabilities encoded in the ENR":
let
enrIp = ValidIpAddress.init("127.0.0.1")
enrTcpPort, enrUdpPort = Port(60000)
enrKey = generateSecp256k1Key()
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]]
# TODO: Refactor enr.Record.init, provide enums as inputs enr.Record.init(capabilites=[Store,Filter])
# TODO: safer than a util function and directly using the bits
# test all flag combinations 2^4 = 16 (b0000-b1111)
records = toSeq(0b0000_0000'u8..0b0000_1111'u8)
.mapIt(enr.Record.init(enrKey,
some(enrIp),
some(enrTcpPort),
some(enrUdpPort),
some(uint8(it)),
multiaddrs))
# same order:  lightpush | filter| store | relay
expectedCapabilities = @[[false, false, false, false],
[false, false, false, true],
[false, false, true, false],
[false, false, true, true],
[false, true, false, false],
[false, true, false, true],
[false, true, true, false],
[false, true, true, true],
[true, false, false, false],
[true, false, false, true],
[true, false, true, false],
[true, false, true, true],
[true, true, false, false],
[true, true, false, true],
[true, true, true, false],
[true, true, true, true]]
for i, record in records:
for j, capability in @[Lightpush, Filter, Store, Relay]:
check expectedCapabilities[i][j] == record.supportsCapability(capability)
test "Get all supported capabilities encoded in the ENR":
let
enrIp = ValidIpAddress.init("127.0.0.1")
enrTcpPort, enrUdpPort = Port(60000)
enrKey = generateSecp256k1Key()
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]]
records = @[0b0000_0000'u8,
0b0000_1111'u8,
0b0000_1001'u8,
0b0000_1110'u8,
0b0000_1000'u8,]
.mapIt(enr.Record.init(enrKey,
some(enrIp),
some(enrTcpPort),
some(enrUdpPort),
some(uint8(it)),
multiaddrs))
# expected capabilities, ordered LSB to MSB
expectedCapabilities: seq[seq[Capabilities]] = @[
#[0b0000_0000]# @[],
#[0b0000_1111]# @[Relay, Store, Filter, Lightpush],
#[0b0000_1001]# @[Relay, Lightpush],
#[0b0000_1110]# @[Store, Filter, Lightpush],
#[0b0000_1000]# @[Lightpush]]
for i, actualExpetedTuple in zip(records, expectedCapabilities):
check actualExpetedTuple[0].getCapabilities() == actualExpetedTuple[1]
test "Get supported capabilities of a non waku node":
# non waku enr, i.e. Ethereum one
let nonWakuEnr = "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2G"&
"xb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNl"&
"Y3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA"
var nonWakuEnrRecord: Record
check:
nonWakuEnrRecord.fromURI(nonWakuEnr)
# check that it doesn't support any capability and it doesnt't break
check:
nonWakuEnrRecord.getCapabilities() == []
nonWakuEnrRecord.supportsCapability(Relay) == false
nonWakuEnrRecord.supportsCapability(Store) == false
nonWakuEnrRecord.supportsCapability(Filter) == false
nonWakuEnrRecord.supportsCapability(Lightpush) == false

View File

@ -89,10 +89,12 @@ procSuite "Waku Peer Exchange":
node3 = WakuNode.new(nodeKey3, bindIp, nodeTcpPort3)
# todo: px flag
flags = initWakuFlags(lightpush = false,
filter = false,
store = false,
relay = true)
flags = CapabilitiesBitfield.init(
lightpush = false,
filter = false,
store = false,
relay = true
)
# Mount discv5
node1.wakuDiscv5 = WakuDiscoveryV5.new(

View File

@ -224,7 +224,7 @@ proc initAndStartNode(conf: NetworkMonitorConf): Result[WakuNode, string] =
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
nodeTcpPort = Port(60000)
nodeUdpPort = Port(9000)
flags = initWakuFlags(lightpush = false, filter = false, store = false, relay = true)
flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true)
try:
let

View File

@ -135,7 +135,7 @@ type NetConfig* = object
enrIp*: Option[ValidIpAddress]
enrPort*: Option[Port]
discv5UdpPort*: Option[Port]
wakuFlags*: Option[WakuEnrBitfield]
wakuFlags*: Option[CapabilitiesBitfield]
bindIp*: ValidIpAddress
bindPort*: Port
@ -151,7 +151,7 @@ proc init*(
wssEnabled: bool = false,
dns4DomainName = none(string),
discv5UdpPort = none(Port),
wakuFlags = none(WakuEnrBitfield)
wakuFlags = none(CapabilitiesBitfield)
): T {.raises: [LPError]} =
## Initialize addresses
let
@ -230,7 +230,7 @@ proc getEnr*(netConfig: NetConfig,
if wakuDiscV5.isSome():
return wakuDiscV5.get().protocol.getRecord()
return enr.Record.init(nodekey,
return enr.Record.init(1, nodekey,
netConfig.enrIp,
netConfig.enrPort,
netConfig.discv5UdpPort,
@ -275,7 +275,7 @@ proc new*(T: type WakuNode,
wssEnabled: bool = false,
secureKey: string = "",
secureCert: string = "",
wakuFlags = none(WakuEnrBitfield),
wakuFlags = none(CapabilitiesBitfield),
nameResolver: NameResolver = nil,
sendSignedPeerRecord = false,
dns4DomainName = none(string),

View File

@ -72,10 +72,10 @@ proc addBootstrapNode*(bootstrapAddr: string,
bootstrapAddr, reason = enrRes.error
proc isWakuNode(node: Node): bool =
let wakuField = node.record.tryGet(WAKU_ENR_FIELD, uint8)
let wakuField = node.record.tryGet(CapabilitiesEnrField, uint8)
if wakuField.isSome():
return wakuField.get().WakuEnrBitfield != 0x00 # True if any flag set to true
return wakuField.get() != 0x00 # True if any flag set to true
return false
@ -116,14 +116,14 @@ proc new*(T: type WakuDiscoveryV5,
bootstrapEnrs = newSeq[enr.Record](),
enrAutoUpdate = false,
privateKey: keys.PrivateKey,
flags: WakuEnrBitfield,
flags: CapabilitiesBitfield,
multiaddrs = newSeq[MultiAddress](),
rng: ref HmacDrbgContext,
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
## TODO: consider loading from a configurable bootstrap file
## We always add the waku field as specified
var enrInitFields = @[(WAKU_ENR_FIELD, @[flags.byte])]
var enrInitFields = @[(CapabilitiesEnrField, @[flags.byte])]
## Add multiaddresses to ENR
if multiaddrs.len > 0:
@ -151,7 +151,7 @@ proc new*(T: type WakuDiscoveryV5,
bootstrapNodes: seq[string],
enrAutoUpdate = false,
privateKey: keys.PrivateKey,
flags: WakuEnrBitfield,
flags: CapabilitiesBitfield,
multiaddrs = newSeq[MultiAddress](),
rng: ref HmacDrbgContext,
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =

View File

@ -19,14 +19,17 @@ import
export enr, crypto, multiaddress, net
const
MULTIADDR_ENR_FIELD* = "multiaddrs"
WAKU_ENR_FIELD* = "waku2"
MultiaddrEnrField* = "multiaddrs"
CapabilitiesEnrField* = "waku2"
## Capabilities
type
## 8-bit flag field to indicate Waku capabilities.
## Only the 4 LSBs are currently defined according
## to RFC31 (https://rfc.vac.dev/spec/31/).
WakuEnrBitfield* = uint8
CapabilitiesBitfield* = distinct uint8
## See: https://rfc.vac.dev/spec/31/#waku2-enr-key
## each enum numbers maps to a bit (where 0 is the LSB)
@ -34,7 +37,59 @@ type
Relay = 0,
Store = 1,
Filter = 2,
Lightpush = 3,
Lightpush = 3
func init*(T: type CapabilitiesBitfield, lightpush, filter, store, relay: bool): T =
## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/)
var bitfield: uint8
if relay: bitfield.setBit(0)
if store: bitfield.setBit(1)
if filter: bitfield.setBit(2)
if lightpush: bitfield.setBit(3)
CapabilitiesBitfield(bitfield)
func init*(T: type CapabilitiesBitfield, caps: varargs[Capabilities]): T =
## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/)
var bitfield: uint8
for cap in caps:
bitfield.setBit(ord(cap))
CapabilitiesBitfield(bitfield)
converter toCapabilitiesBitfield*(field: uint8): CapabilitiesBitfield =
CapabilitiesBitfield(field)
proc supportsCapability*(bitfield: CapabilitiesBitfield, cap: Capabilities): bool =
testBit(bitfield.uint8, ord(cap))
func toCapabilities*(bitfield: CapabilitiesBitfield): seq[Capabilities] =
toSeq(Capabilities.low..Capabilities.high).filterIt(supportsCapability(bitfield, it))
func toFieldPair*(caps: CapabilitiesBitfield): FieldPair =
toFieldPair(CapabilitiesEnrField, @[caps.uint8])
proc getCapabilitiesField*(r: Record): EnrResult[CapabilitiesBitfield] =
let field = ?r.get(CapabilitiesEnrField, seq[uint8])
ok(CapabilitiesBitfield(field[0]))
proc supportsCapability*(r: Record, cap: Capabilities): bool =
let bitfield = getCapabilitiesField(r)
if bitfield.isErr():
return false
bitfield.value.supportsCapability(cap)
proc getCapabilities*(r: Record): seq[Capabilities] =
let bitfield = getCapabilitiesField(r)
if bitfield.isErr():
return @[]
bitfield.value.toCapabilities()
## Multiaddress
func getRawField*(multiaddrs: seq[MultiAddress]): seq[byte] =
var fieldRaw: seq[byte]
@ -93,28 +148,6 @@ func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq
return ok(slicedSeq)
################
# Public utils #
################
func initWakuFlags*(lightpush, filter, store, relay: bool): WakuEnrBitfield =
## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/)
var v = 0b0000_0000'u8
if lightpush: v.setBit(3)
if filter: v.setBit(2)
if store: v.setBit(1)
if relay: v.setBit(0)
# TODO: With the changes in this PR, this can be refactored? Using the enum?
# Perhaps refactor to:
# WaKuEnr.enr.Record.init(..., capabilities=[Store, Lightpush])
# WaKuEnr.enr.Record.init(..., capabilities=[Store, Lightpush, Relay, Filter])
# Safer also since we dont inject WakuEnrBitfield, and we let this package
# handle the bits according to the capabilities
return v.WakuEnrBitfield
func toMultiAddresses*(multiaddrsField: seq[byte]): seq[MultiAddress] =
## Parses a `multiaddrs` ENR field according to
## https://rfc.vac.dev/spec/31/
@ -147,11 +180,16 @@ func toMultiAddresses*(multiaddrsField: seq[byte]): seq[MultiAddress] =
return multiaddrs
## ENR
func init*(T: type enr.Record,
seqNum: uint64,
privateKey: crypto.PrivateKey,
enrIp: Option[ValidIpAddress],
enrTcpPort, enrUdpPort: Option[Port],
wakuFlags = none(WakuEnrBitfield),
enrIp = none(ValidIpAddress),
enrTcpPort = none(Port),
enrUdpPort = none(Port),
wakuFlags = none(CapabilitiesBitfield),
multiaddrs: seq[MultiAddress] = @[]): T =
assert privateKey.scheme == PKScheme.Secp256k1
@ -160,27 +198,22 @@ func init*(T: type enr.Record,
var wakuEnrFields: seq[FieldPair]
# `waku2` field
if wakuFlags.isSome:
wakuEnrFields.add(toFieldPair(WAKU_ENR_FIELD, @[wakuFlags.get().byte]))
if wakuFlags.isSome():
wakuEnrFields.add(toFieldPair(wakuFlags.get()))
# `multiaddrs` field
if multiaddrs.len > 0:
wakuEnrFields.add(multiaddrs.stripPeerIds().toFieldPair)
wakuEnrFields.add(multiaddrs.stripPeerIds().toFieldPair())
let
rawPk = privateKey.getRawBytes().expect("Private key is valid")
pk = keys.PrivateKey.fromRaw(rawPk).expect("Raw private key is of valid length")
enr = enr.Record.init(1, pk,
enrIp, enrTcpPort, enrUdpPort,
wakuEnrFields).expect("Record within size limits")
return enr
proc supportsCapability*(r: Record, capability: Capabilities): bool =
let enrCapabilities = r.get(WAKU_ENR_FIELD, seq[byte])
if enrCapabilities.isOk():
return testBit(enrCapabilities.get()[0], capability.ord)
return false
proc getCapabilities*(r: Record): seq[Capabilities] =
return toSeq(Capabilities.low..Capabilities.high).filterIt(r.supportsCapability(it))
enr.Record.init(
seqNum=seqNum,
pk=pk,
ip=enrIp,
tcpPort=enrTcpPort,
udpPort=enrUdpPort,
extraFields=wakuEnrFields
).expect("Record within size limits")