feat(discv5): advertise custom multiaddresses (#1512)

* feat(discv5): allow custom multiaddr advertisement in discv5

feat(discv5): allow custom multiaddr advertisement in discv5

feat(discv5): move discv5 setup from wakunode2 to waku_node

fix(waku_node): def param

test(discv5): test for ext multiaddr

fix(discv5): address comments

fix(discv5): address comments

fix(wakunode2): discoveryconfig in var before init

fix(discv5): pass multiaddr to discv5 directly

fix(discv5): make multiaddrs optional

fix(test): discv5 init

fix(discv5): split discv5 mounting from waku_node

chore(discv5): s/WakuAddressMetadata/WakuNodeAddrMeta/g

* fix(waku_node): 1.25 max conns

* fix(discv5): s/WakuNodeAddrMeta/NetConfig/g

* fix(discv5): address reviews

* fix(discv5): smaller try-catches

* fix(discv5): missing arg

* fix: compile error
This commit is contained in:
Aaryamann Challani 2023-02-07 18:36:50 +05:30 committed by GitHub
parent 7f2ea1caeb
commit 9ddf0fe1e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 379 additions and 166 deletions

View File

@ -278,61 +278,77 @@ proc initNode(conf: WakuNodeConf,
let pStorage = if peerStore.isNone(): nil let pStorage = if peerStore.isNone(): nil
else: peerStore.get() else: peerStore.get()
let rng = crypto.newRng()
# Wrap in none because NetConfig does not have a default constructor
# TODO: We could change bindIp in NetConfig to be something less restrictive than ValidIpAddress,
# which doesn't allow default construction
var netConfigOpt = none(NetConfig)
try: try:
node = WakuNode.new(nodekey, netConfigOpt = some(NetConfig.init(
conf.listenAddress, Port(uint16(conf.tcpPort) + conf.portsShift), bindIp = conf.listenAddress,
extIp, extPort, bindPort = Port(uint16(conf.tcpPort) + conf.portsShift),
extMultiAddrs, extIp = extIp,
pStorage, extPort = extPort,
conf.maxConnections.int, extMultiAddrs = extMultiAddrs,
Port(uint16(conf.websocketPort) + conf.portsShift), wsBindPort = Port(uint16(conf.websocketPort) + conf.portsShift),
conf.websocketSupport, wsEnabled = conf.websocketSupport,
conf.websocketSecureSupport, wssEnabled = conf.websocketSecureSupport,
conf.websocketSecureKeyPath, dns4DomainName = dns4DomainName,
conf.websocketSecureCertPath, discv5UdpPort = discv5UdpPort,
some(wakuFlags), wakuFlags = some(wakuFlags),
dnsResolver, ))
conf.relayPeerExchange, # We send our own signed peer record when peer exchange enabled
dns4DomainName,
discv5UdpPort,
some(conf.agentString),
conf.peerStoreCapacity,
rng)
except: except:
return err("failed to create waku node instance: " & getCurrentExceptionMsg()) return err("failed to create net config instance: " & getCurrentExceptionMsg())
let netConfig = netConfigOpt.get()
var wakuDiscv5 = none(WakuDiscoveryV5)
if conf.discv5Discovery: if conf.discv5Discovery:
let let dynamicBootstrapEnrs = dynamicBootstrapNodes
discoveryConfig = DiscoveryConfig.init( .filterIt(it.hasUdpPort())
conf.discv5TableIpLimit, conf.discv5BucketIpLimit, conf.discv5BitsPerHop) .mapIt(it.enr.get())
# select dynamic bootstrap nodes that have an ENR containing a udp port.
# Discv5 only supports UDP https://github.com/ethereum/devp2p/blob/master/discv5/discv5-theory.md)
var discv5BootstrapEnrs: seq[enr.Record] var discv5BootstrapEnrs: seq[enr.Record]
for n in dynamicBootstrapNodes:
if n.enr.isSome():
let
enr = n.enr.get()
tenrRes = enr.toTypedRecord()
if tenrRes.isOk() and (tenrRes.get().udp.isSome() or tenrRes.get().udp6.isSome()):
discv5BootstrapEnrs.add(enr)
# parse enrURIs from the configuration and add the resulting ENRs to the discv5BootstrapEnrs seq # parse enrURIs from the configuration and add the resulting ENRs to the discv5BootstrapEnrs seq
for enrUri in conf.discv5BootstrapNodes: for enrUri in conf.discv5BootstrapNodes:
addBootstrapNode(enrUri, discv5BootstrapEnrs) addBootstrapNode(enrUri, discv5BootstrapEnrs)
discv5BootstrapEnrs.add(dynamicBootstrapEnrs)
node.wakuDiscv5 = WakuDiscoveryV5.new( let discv5Config = DiscoveryConfig.init(conf.discv5TableIpLimit,
extIP, extPort, discv5UdpPort, conf.discv5BucketIpLimit,
conf.listenAddress, conf.discv5BitsPerHop)
discv5UdpPort.get(), try:
discv5BootstrapEnrs, wakuDiscv5 = some(WakuDiscoveryV5.new(
conf.discv5EnrAutoUpdate, extIp = netConfig.extIp,
keys.PrivateKey(nodekey.skkey), extTcpPort = netConfig.extPort,
wakuFlags, extUdpPort = netConfig.discv5UdpPort,
[], # Empty enr fields, for now bindIp = netConfig.bindIp,
node.rng, discv5UdpPort = netConfig.discv5UdpPort.get(),
discoveryConfig bootstrapEnrs = discv5BootstrapEnrs,
) enrAutoUpdate = conf.discv5EnrAutoUpdate,
privateKey = keys.PrivateKey(nodekey.skkey),
flags = netConfig.wakuFlags.get(),
multiaddrs = netConfig.enrMultiaddrs,
rng = rng,
discv5Config = discv5Config,
))
except:
return err("failed to create waku discv5 instance: " & getCurrentExceptionMsg())
try:
node = WakuNode.new(nodekey = nodekey,
netConfig = netConfig,
peerStorage = pStorage,
maxConnections = conf.maxConnections.int,
secureKey = conf.websocketSecureKeyPath,
secureCert = conf.websocketSecureCertPath,
nameResolver = dnsResolver,
sendSignedPeerRecord = conf.relayPeerExchange, # We send our own signed peer record when peer exchange enabled
wakuDiscv5 = wakuDiscv5,
agentString = some(conf.agentString),
peerStoreCapacity = conf.peerStoreCapacity,
rng = rng)
except:
return err("failed to create waku node instance: " & getCurrentExceptionMsg())
ok(node) ok(node)

View File

@ -48,7 +48,6 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
bootstrapNodes = bootstrapNodes, bootstrapNodes = bootstrapNodes,
privateKey = keys.PrivateKey(nodeKey.skkey), privateKey = keys.PrivateKey(nodeKey.skkey),
flags = flags, flags = flags,
enrFields = [],
rng = node.rng) rng = node.rng)
await node.start() await node.start()

View File

@ -44,7 +44,6 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
bootstrapNodes = bootstrapNodes, bootstrapNodes = bootstrapNodes,
privateKey = keys.PrivateKey(nodeKey.skkey), privateKey = keys.PrivateKey(nodeKey.skkey),
flags = flags, flags = flags,
enrFields = [],
rng = node.rng) rng = node.rng)
await node.start() await node.start()

View File

@ -22,10 +22,10 @@ procSuite "ENR utils":
tooShort = "0x000A047F0000010601BADD0301".hexToSeqByte() tooShort = "0x000A047F0000010601BADD0301".hexToSeqByte()
gibberish = "0x3270ac4e5011123c".hexToSeqByte() gibberish = "0x3270ac4e5011123c".hexToSeqByte()
empty = newSeq[byte]() empty = newSeq[byte]()
## Note: we expect to fail optimistically, i.e. extract ## Note: we expect to fail optimistically, i.e. extract
## any addresses we can and ignore other errors. ## any addresses we can and ignore other errors.
## Worst case scenario is we return an empty `multiaddrs` seq. ## Worst case scenario is we return an empty `multiaddrs` seq.
check: check:
# Expected cases # Expected cases
reasonable.toMultiAddresses().contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]) reasonable.toMultiAddresses().contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[])
@ -50,19 +50,19 @@ procSuite "ENR utils":
MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]] MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]]
let let
record = initEnr(enrKey, some(enrIp), record = enr.Record.init(enrKey, some(enrIp),
some(enrTcpPort), some(enrUdpPort), some(enrTcpPort), some(enrUdpPort),
some(wakuFlags), some(wakuFlags),
multiaddrs) multiaddrs)
typedRecord = record.toTypedRecord.get() typedRecord = record.toTypedRecord.get()
# Check EIP-778 ENR fields # Check EIP-778 ENR fields
check: check:
@(typedRecord.secp256k1.get()) == enrKey.getPublicKey()[].getRawBytes()[] @(typedRecord.secp256k1.get()) == enrKey.getPublicKey()[].getRawBytes()[]
ipv4(typedRecord.ip.get()) == enrIp ipv4(typedRecord.ip.get()) == enrIp
Port(typedRecord.tcp.get()) == enrTcpPort Port(typedRecord.tcp.get()) == enrTcpPort
Port(typedRecord.udp.get()) == enrUdpPort Port(typedRecord.udp.get()) == enrUdpPort
# Check Waku ENR fields # Check Waku ENR fields
let let
decodedFlags = record.get(WAKU_ENR_FIELD, seq[byte])[] decodedFlags = record.get(WAKU_ENR_FIELD, seq[byte])[]
@ -71,7 +71,7 @@ procSuite "ENR utils":
decodedFlags == @[wakuFlags.byte] 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/442/ws")[])
decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]) decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[])
asyncTest "Strip multiaddr peerId": asyncTest "Strip multiaddr peerId":
# Tests that peerId is stripped of multiaddrs as per RFC31 # Tests that peerId is stripped of multiaddrs as per RFC31
let let
@ -81,7 +81,7 @@ procSuite "ENR utils":
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD")[]] multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD")[]]
let let
record = initEnr(enrKey, some(enrIp), record = enr.Record.init(enrKey, some(enrIp),
some(enrTcpPort), some(enrUdpPort), some(enrTcpPort), some(enrUdpPort),
none(WakuEnrBitfield), none(WakuEnrBitfield),
multiaddrs) multiaddrs)
@ -89,7 +89,7 @@ procSuite "ENR utils":
# Check Waku ENR fields # Check Waku ENR fields
let let
decodedAddrs = record.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses() decodedAddrs = record.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
check decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]) # Peer Id has been stripped check decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]) # Peer Id has been stripped
asyncTest "Decode ENR with multiaddrs field": asyncTest "Decode ENR with multiaddrs field":
@ -110,7 +110,7 @@ procSuite "ENR utils":
var enrRecord: Record var enrRecord: Record
check: check:
enrRecord.fromURI(knownEnr) enrRecord.fromURI(knownEnr)
let typedRecord = enrRecord.toTypedRecord.get() let typedRecord = enrRecord.toTypedRecord.get()
# Check EIP-778 ENR fields # Check EIP-778 ENR fields
@ -118,11 +118,11 @@ procSuite "ENR utils":
ipv4(typedRecord.ip.get()) == knownIp ipv4(typedRecord.ip.get()) == knownIp
typedRecord.tcp == knownTcpPort typedRecord.tcp == knownTcpPort
typedRecord.udp == knownUdpPort typedRecord.udp == knownUdpPort
# Check Waku ENR fields # Check Waku ENR fields
let let
decodedAddrs = enrRecord.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses() decodedAddrs = enrRecord.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
for knownMultiaddr in knownMultiaddrs: for knownMultiaddr in knownMultiaddrs:
check decodedAddrs.contains(knownMultiaddr) check decodedAddrs.contains(knownMultiaddr)
@ -132,12 +132,12 @@ procSuite "ENR utils":
enrTcpPort, enrUdpPort = Port(60000) enrTcpPort, enrUdpPort = Port(60000)
enrKey = wakuenr.crypto.PrivateKey.random(Secp256k1, rng[])[] enrKey = wakuenr.crypto.PrivateKey.random(Secp256k1, rng[])[]
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]] multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]]
# TODO: Refactor initEnr, provide enums as inputs initEnr(capabilites=[Store,Filter]) # 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 # TODO: safer than a util function and directly using the bits
# test all flag combinations 2^4 = 16 (b0000-b1111) # test all flag combinations 2^4 = 16 (b0000-b1111)
records = toSeq(0b0000_0000'u8..0b0000_1111'u8) records = toSeq(0b0000_0000'u8..0b0000_1111'u8)
.mapIt(initEnr(enrKey, .mapIt(enr.Record.init(enrKey,
some(enrIp), some(enrIp),
some(enrTcpPort), some(enrTcpPort),
some(enrUdpPort), some(enrUdpPort),
@ -161,7 +161,7 @@ procSuite "ENR utils":
[true, true, false, true], [true, true, false, true],
[true, true, true, false], [true, true, true, false],
[true, true, true, true]] [true, true, true, true]]
for i, record in records: for i, record in records:
for j, capability in @[Lightpush, Filter, Store, Relay]: for j, capability in @[Lightpush, Filter, Store, Relay]:
check expectedCapabilities[i][j] == record.supportsCapability(capability) check expectedCapabilities[i][j] == record.supportsCapability(capability)
@ -172,18 +172,18 @@ procSuite "ENR utils":
enrTcpPort, enrUdpPort = Port(60000) enrTcpPort, enrUdpPort = Port(60000)
enrKey = wakuenr.crypto.PrivateKey.random(Secp256k1, rng[])[] enrKey = wakuenr.crypto.PrivateKey.random(Secp256k1, rng[])[]
multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]] multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]]
records = @[0b0000_0000'u8, records = @[0b0000_0000'u8,
0b0000_1111'u8, 0b0000_1111'u8,
0b0000_1001'u8, 0b0000_1001'u8,
0b0000_1110'u8, 0b0000_1110'u8,
0b0000_1000'u8,] 0b0000_1000'u8,]
.mapIt(initEnr(enrKey, .mapIt(enr.Record.init(enrKey,
some(enrIp), some(enrIp),
some(enrTcpPort), some(enrTcpPort),
some(enrUdpPort), some(enrUdpPort),
some(uint8(it)), some(uint8(it)),
multiaddrs)) multiaddrs))
# expected capabilities, ordered LSB to MSB # expected capabilities, ordered LSB to MSB
expectedCapabilities: seq[seq[Capabilities]] = @[ expectedCapabilities: seq[seq[Capabilities]] = @[
@ -197,14 +197,14 @@ procSuite "ENR utils":
check actualExpetedTuple[0].getCapabilities() == actualExpetedTuple[1] check actualExpetedTuple[0].getCapabilities() == actualExpetedTuple[1]
asyncTest "Get supported capabilities of a non waku node": asyncTest "Get supported capabilities of a non waku node":
# non waku enr, i.e. Ethereum one # non waku enr, i.e. Ethereum one
let nonWakuEnr = "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2G"& let nonWakuEnr = "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2G"&
"xb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNl"& "xb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNl"&
"Y3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA" "Y3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA"
var nonWakuEnrRecord: Record var nonWakuEnrRecord: Record
check: check:
nonWakuEnrRecord.fromURI(nonWakuEnr) nonWakuEnrRecord.fromURI(nonWakuEnr)
@ -214,4 +214,4 @@ procSuite "ENR utils":
nonWakuEnrRecord.supportsCapability(Relay) == false nonWakuEnrRecord.supportsCapability(Relay) == false
nonWakuEnrRecord.supportsCapability(Store) == false nonWakuEnrRecord.supportsCapability(Store) == false
nonWakuEnrRecord.supportsCapability(Filter) == false nonWakuEnrRecord.supportsCapability(Filter) == false
nonWakuEnrRecord.supportsCapability(Lightpush) == false nonWakuEnrRecord.supportsCapability(Lightpush) == false

View File

@ -2,6 +2,7 @@
import import
chronos, chronos,
chronicles,
testutils/unittests, testutils/unittests,
stew/byteutils, stew/byteutils,
stew/shims/net, stew/shims/net,
@ -25,7 +26,7 @@ procSuite "Waku Discovery v5":
nodeTcpPort1 = Port(61500) nodeTcpPort1 = Port(61500)
nodeUdpPort1 = Port(9000) nodeUdpPort1 = Port(9000)
node1 = WakuNode.new(nodeKey1, bindIp, nodeTcpPort1) node1 = WakuNode.new(nodeKey1, bindIp, nodeTcpPort1)
nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[] nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[]
nodeTcpPort2 = Port(61502) nodeTcpPort2 = Port(61502)
nodeUdpPort2 = Port(9002) nodeUdpPort2 = Port(9002)
@ -46,7 +47,7 @@ procSuite "Waku Discovery v5":
contentTopic = ContentTopic("/waku/2/default-content/proto") contentTopic = ContentTopic("/waku/2/default-content/proto")
payload = "Can you see me?".toBytes() payload = "Can you see me?".toBytes()
message = WakuMessage(payload: payload, contentTopic: contentTopic) message = WakuMessage(payload: payload, contentTopic: contentTopic)
# Mount discv5 # Mount discv5
node1.wakuDiscv5 = WakuDiscoveryV5.new( node1.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort1), some(nodeUdpPort1), some(extIp), some(nodeTcpPort1), some(nodeUdpPort1),
@ -56,10 +57,10 @@ procSuite "Waku Discovery v5":
false, false,
keys.PrivateKey(nodeKey1.skkey), keys.PrivateKey(nodeKey1.skkey),
flags, flags,
[], # Empty enr fields, for now newSeq[MultiAddress](), # Empty multiaddr fields, for now
node1.rng node1.rng
) )
node2.wakuDiscv5 = WakuDiscoveryV5.new( node2.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort2), some(nodeUdpPort2), some(extIp), some(nodeTcpPort2), some(nodeUdpPort2),
bindIp, bindIp,
@ -68,10 +69,10 @@ procSuite "Waku Discovery v5":
false, false,
keys.PrivateKey(nodeKey2.skkey), keys.PrivateKey(nodeKey2.skkey),
flags, flags,
[], # Empty enr fields, for now newSeq[MultiAddress](), # Empty multiaddr fields, for now
node2.rng node2.rng
) )
node3.wakuDiscv5 = WakuDiscoveryV5.new( node3.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort3), some(nodeUdpPort3), some(extIp), some(nodeTcpPort3), some(nodeUdpPort3),
bindIp, bindIp,
@ -80,7 +81,7 @@ procSuite "Waku Discovery v5":
false, false,
keys.PrivateKey(nodeKey3.skkey), keys.PrivateKey(nodeKey3.skkey),
flags, flags,
[], # Empty enr fields, for now newSeq[MultiAddress](), # Empty multiaddr fields, for now
node3.rng node3.rng
) )
@ -97,7 +98,7 @@ procSuite "Waku Discovery v5":
node1.wakuDiscv5.protocol.nodesDiscovered > 0 node1.wakuDiscv5.protocol.nodesDiscovered > 0
node2.wakuDiscv5.protocol.nodesDiscovered > 0 node2.wakuDiscv5.protocol.nodesDiscovered > 0
node3.wakuDiscv5.protocol.nodesDiscovered > 0 node3.wakuDiscv5.protocol.nodesDiscovered > 0
# Let's see if we can deliver a message end-to-end # Let's see if we can deliver a message end-to-end
# var completionFut = newFuture[bool]() # var completionFut = newFuture[bool]()
# proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} = # proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} =
@ -119,3 +120,76 @@ procSuite "Waku Discovery v5":
# (await completionFut.withTimeout(6.seconds)) == true # (await completionFut.withTimeout(6.seconds)) == true
await allFutures([node1.stop(), node2.stop(), node3.stop()]) await allFutures([node1.stop(), node2.stop(), node3.stop()])
asyncTest "Custom multiaddresses are advertised correctly":
let
bindIp = ValidIpAddress.init("0.0.0.0")
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)
nodeTcpPort1 = Port(9010)
nodeUdpPort1 = Port(9012)
node1Key = generateKey()
node1NetConfig = NetConfig.init(bindIp = bindIp,
extIp = some(extIp),
extPort = some(nodeTcpPort1),
bindPort = nodeTcpPort1,
extmultiAddrs = @[expectedMultiAddr],
wakuFlags = some(flags),
discv5UdpPort = some(nodeUdpPort1))
node1discV5 = WakuDiscoveryV5.new(extIp = node1NetConfig.extIp,
extTcpPort = node1NetConfig.extPort,
extUdpPort = node1NetConfig.discv5UdpPort,
bindIp = node1NetConfig.bindIp,
discv5UdpPort = node1NetConfig.discv5UdpPort.get(),
privateKey = keys.PrivateKey(node1Key.skkey),
multiaddrs = node1NetConfig.enrMultiaddrs,
flags = node1NetConfig.wakuFlags.get(),
rng = rng)
node1 = WakuNode.new(nodekey = node1Key,
netConfig = node1NetConfig,
wakuDiscv5 = some(node1discV5),
rng = rng)
nodeTcpPort2 = Port(9014)
nodeUdpPort2 = Port(9016)
node2Key = generateKey()
node2NetConfig = NetConfig.init(bindIp = bindIp,
extIp = some(extIp),
extPort = some(nodeTcpPort2),
bindPort = nodeTcpPort2,
wakuFlags = some(flags),
discv5UdpPort = some(nodeUdpPort2))
node2discV5 = WakuDiscoveryV5.new(extIp = node2NetConfig.extIp,
extTcpPort = node2NetConfig.extPort,
extUdpPort = node2NetConfig.discv5UdpPort,
bindIp = node2NetConfig.bindIp,
discv5UdpPort = node2NetConfig.discv5UdpPort.get(),
bootstrapEnrs = @[node1.wakuDiscv5.protocol.localNode.record],
privateKey = keys.PrivateKey(node2Key.skkey),
flags = node2NetConfig.wakuFlags.get(),
rng = rng)
node2 = WakuNode.new(nodeKey = node2Key,
netConfig = node2NetConfig,
wakuDiscv5 = some(node2discV5))
await allFutures([node1.start(), node2.start()])
await allFutures([node1.startDiscv5(), node2.startDiscv5()])
await sleepAsync(3000.millis) # Give the algorithm some time to work its magic
let node1Enr = node2.wakuDiscv5.protocol.routingTable.buckets[0].nodes[0].record
let multiaddrs = node1Enr.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses()
check:
node1.wakuDiscv5.protocol.nodesDiscovered > 0
node2.wakuDiscv5.protocol.nodesDiscovered > 0
multiaddrs.contains(expectedMultiAddr)

View File

@ -103,7 +103,7 @@ procSuite "Waku Peer Exchange":
false, false,
keys.PrivateKey(nodeKey1.skkey), keys.PrivateKey(nodeKey1.skkey),
flags, flags,
[], # Empty enr fields, for now newSeq[MultiAddress](), # Empty multiaddr fields, for now
node1.rng node1.rng
) )
@ -115,7 +115,7 @@ procSuite "Waku Peer Exchange":
false, false,
keys.PrivateKey(nodeKey2.skkey), keys.PrivateKey(nodeKey2.skkey),
flags, flags,
[], # Empty enr fields, for now newSeq[MultiAddress](), # Empty multiaddr fields, for now
node2.rng node2.rng
) )

View File

@ -112,18 +112,20 @@ proc new*(T: type WakuDiscoveryV5,
extTcpPort, extUdpPort: Option[Port], extTcpPort, extUdpPort: Option[Port],
bindIP: ValidIpAddress, bindIP: ValidIpAddress,
discv5UdpPort: Port, discv5UdpPort: Port,
bootstrapEnrs: seq[enr.Record], bootstrapEnrs = newSeq[enr.Record](),
enrAutoUpdate = false, enrAutoUpdate = false,
privateKey: keys.PrivateKey, privateKey: keys.PrivateKey,
flags: WakuEnrBitfield, flags: WakuEnrBitfield,
enrFields: openArray[(string, seq[byte])], multiaddrs = newSeq[MultiAddress](),
rng: ref HmacDrbgContext, rng: ref HmacDrbgContext,
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T = discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
## TODO: consider loading from a configurable bootstrap file ## TODO: consider loading from a configurable bootstrap file
## We always add the waku field as specified ## We always add the waku field as specified
var enrInitFields = @[(WAKU_ENR_FIELD, @[flags.byte])] var enrInitFields = @[(WAKU_ENR_FIELD, @[flags.byte])]
enrInitFields.add(enrFields)
## Add multiaddresses to ENR
enrInitFields.add((MULTIADDR_ENR_FIELD, multiaddrs.getRawField()))
let protocol = newProtocol( let protocol = newProtocol(
privateKey, privateKey,
@ -148,7 +150,7 @@ proc new*(T: type WakuDiscoveryV5,
enrAutoUpdate = false, enrAutoUpdate = false,
privateKey: keys.PrivateKey, privateKey: keys.PrivateKey,
flags: WakuEnrBitfield, flags: WakuEnrBitfield,
enrFields: openArray[(string, seq[byte])], multiaddrs = newSeq[MultiAddress](),
rng: ref HmacDrbgContext, rng: ref HmacDrbgContext,
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T = discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
@ -164,7 +166,7 @@ proc new*(T: type WakuDiscoveryV5,
enrAutoUpdate, enrAutoUpdate,
privateKey, privateKey,
flags, flags,
enrFields, multiaddrs,
rng, rng,
discv5Config discv5Config
) )

View File

@ -131,38 +131,47 @@ template wsFlag(wssEnabled: bool): MultiAddress =
if wssEnabled: MultiAddress.init("/wss").tryGet() if wssEnabled: MultiAddress.init("/wss").tryGet()
else: MultiAddress.init("/ws").tryGet() else: MultiAddress.init("/ws").tryGet()
proc new*(T: type WakuNode, type NetConfig* = object
nodeKey: crypto.PrivateKey, hostAddress*: MultiAddress
bindIp: ValidIpAddress, wsHostAddress*: Option[MultiAddress]
bindPort: Port, hostExtAddress*: Option[MultiAddress]
extIp = none(ValidIpAddress), wsExtAddress*: Option[MultiAddress]
extPort = none(Port), wssEnabled*: bool
extMultiAddrs = newSeq[MultiAddress](), extIp*: Option[ValidIpAddress]
peerStorage: PeerStorage = nil, extPort*: Option[Port]
maxConnections = builders.MaxConnections, dns4DomainName*: Option[string]
wsBindPort: Port = (Port)8000, announcedAddresses*: seq[MultiAddress]
wsEnabled: bool = false, extMultiAddrs*: seq[MultiAddress]
wssEnabled: bool = false, enrMultiAddrs*: seq[MultiAddress]
secureKey: string = "", enrIp*: Option[ValidIpAddress]
secureCert: string = "", enrPort*: Option[Port]
wakuFlags = none(WakuEnrBitfield), discv5UdpPort*: Option[Port]
nameResolver: NameResolver = nil, wakuFlags*: Option[WakuEnrBitfield]
sendSignedPeerRecord = false, bindIp*: ValidIpAddress
dns4DomainName = none(string), bindPort*: Port
discv5UdpPort = none(Port),
agentString = none(string), # defaults to nim-libp2p version
peerStoreCapacity = none(int), # defaults to 1.25 maxConnections
# TODO: make this argument required after tests are updated
rng: ref HmacDrbgContext = crypto.newRng()
): T {.raises: [Defect, LPError, IOError, TLSStreamProtocolError].} =
## Creates a Waku Node instance.
proc init*(
T: type NetConfig,
bindIp: ValidIpAddress,
bindPort: Port,
extIp = none(ValidIpAddress),
extPort = none(Port),
extMultiAddrs = newSeq[MultiAddress](),
wsBindPort: Port = (Port)8000,
wsEnabled: bool = false,
wssEnabled: bool = false,
dns4DomainName = none(string),
discv5UdpPort = none(Port),
wakuFlags = none(WakuEnrBitfield)
): T {.raises: [LPError]} =
## Initialize addresses ## Initialize addresses
let let
# Bind addresses # Bind addresses
hostAddress = ip4TcpEndPoint(bindIp, bindPort) hostAddress = ip4TcpEndPoint(bindIp, bindPort)
wsHostAddress = if wsEnabled or wssEnabled: some(ip4TcpEndPoint(bindIp, wsbindPort) & wsFlag(wssEnabled)) wsHostAddress = if wsEnabled or wssEnabled: some(ip4TcpEndPoint(bindIp, wsbindPort) & wsFlag(wssEnabled))
else: none(MultiAddress) else: none(MultiAddress)
enrIp = if extIp.isSome(): extIp else: some(bindIp)
enrPort = if extPort.isSome(): extPort else: some(bindPort)
# Setup external addresses, if available # Setup external addresses, if available
var var
@ -198,25 +207,48 @@ proc new*(T: type WakuNode,
elif wsHostAddress.isSome(): elif wsHostAddress.isSome():
announcedAddresses.add(wsHostAddress.get()) announcedAddresses.add(wsHostAddress.get())
## Initialize peer
let let
enrIp = if extIp.isSome(): extIp
else: some(bindIp)
enrTcpPort = if extPort.isSome(): extPort
else: some(bindPort)
# enrMultiaddrs are just addresses which cannot be represented in ENR, as described in # enrMultiaddrs are just addresses which cannot be represented in ENR, as described in
# https://rfc.vac.dev/spec/31/#many-connection-types # https://rfc.vac.dev/spec/31/#many-connection-types
enrMultiaddrs = announcedAddresses.filterIt(it.hasProtocol("dns4") or enrMultiaddrs = announcedAddresses.filterIt(it.hasProtocol("dns4") or
it.hasProtocol("dns6") or it.hasProtocol("dns6") or
it.hasProtocol("ws") or it.hasProtocol("ws") or
it.hasProtocol("wss")) it.hasProtocol("wss"))
enr = initEnr(nodeKey,
enrIp,
enrTcpPort,
discv5UdpPort,
wakuFlags,
enrMultiaddrs)
return NetConfig(
hostAddress: hostAddress,
wsHostAddress: wsHostAddress,
hostExtAddress: hostExtAddress,
wsExtAddress: wsExtAddress,
extIp: extIp,
extPort: extPort,
wssEnabled: wssEnabled,
dns4DomainName: dns4DomainName,
announcedAddresses: announcedAddresses,
extMultiAddrs: extMultiAddrs,
enrMultiaddrs: enrMultiaddrs,
enrIp: enrIp,
enrPort: enrPort,
discv5UdpPort: discv5UdpPort,
bindIp: bindIp,
bindPort: bindPort,
wakuFlags: wakuFlags)
proc getEnr*(netConfig: NetConfig,
wakuDiscV5 = none(WakuDiscoveryV5),
nodeKey: crypto.PrivateKey): enr.Record =
if wakuDiscV5.isSome():
return wakuDiscV5.get().protocol.getRecord()
return enr.Record.init(nodekey,
netConfig.enrIp,
netConfig.enrPort,
netConfig.discv5UdpPort,
netConfig.wakuFlags,
netConfig.enrMultiaddrs)
proc getAutonatService*(rng = crypto.newRng()): AutonatService =
## AutonatService request other peers to dial us back ## AutonatService request other peers to dial us back
## flagging us as Reachable or NotReachable. ## flagging us as Reachable or NotReachable.
## minConfidence is used as threshold to determine the state. ## minConfidence is used as threshold to determine the state.
@ -237,36 +269,107 @@ proc new*(T: type WakuNode,
autonatService.statusAndConfidenceHandler(statusAndConfidenceHandler) autonatService.statusAndConfidenceHandler(statusAndConfidenceHandler)
info "Initializing networking", addrs=announcedAddresses return autonatService
## retain old signature, but deprecate it
proc new*(T: type WakuNode,
nodeKey: crypto.PrivateKey,
bindIp: ValidIpAddress,
bindPort: Port,
extIp = none(ValidIpAddress),
extPort = none(Port),
extMultiAddrs = newSeq[MultiAddress](),
peerStorage: PeerStorage = nil,
maxConnections = builders.MaxConnections,
wsBindPort: Port = (Port)8000,
wsEnabled: bool = false,
wssEnabled: bool = false,
secureKey: string = "",
secureCert: string = "",
wakuFlags = none(WakuEnrBitfield),
nameResolver: NameResolver = nil,
sendSignedPeerRecord = false,
dns4DomainName = none(string),
discv5UdpPort = none(Port),
wakuDiscv5 = none(WakuDiscoveryV5),
agentString = none(string), # defaults to nim-libp2p version
peerStoreCapacity = none(int), # defaults to 1.25 maxConnections
# TODO: make this argument required after tests are updated
rng: ref HmacDrbgContext = crypto.newRng()
): T {.raises: [Defect, LPError, IOError, TLSStreamProtocolError], deprecated: "Use NetConfig variant".} =
let netConfig = NetConfig.init(
bindIp = bindIp,
bindPort = bindPort,
extIp = extIp,
extPort = extPort,
extMultiAddrs = extMultiAddrs,
wsBindPort = wsBindPort,
wsEnabled = wsEnabled,
wssEnabled = wssEnabled,
wakuFlags = wakuFlags,
dns4DomainName = dns4DomainName,
discv5UdpPort = discv5UdpPort,
)
return WakuNode.new(
nodeKey = nodeKey,
netConfig = netConfig,
peerStorage = peerStorage,
maxConnections = maxConnections,
secureKey = secureKey,
secureCert = secureCert,
nameResolver = nameResolver,
sendSignedPeerRecord = sendSignedPeerRecord,
wakuDiscv5 = wakuDiscv5,
agentString = agentString,
peerStoreCapacity = peerStoreCapacity,
)
proc new*(T: type WakuNode,
nodeKey: crypto.PrivateKey,
netConfig: NetConfig,
peerStorage: PeerStorage = nil,
maxConnections = builders.MaxConnections,
secureKey: string = "",
secureCert: string = "",
nameResolver: NameResolver = nil,
sendSignedPeerRecord = false,
wakuDiscv5 = none(WakuDiscoveryV5),
agentString = none(string), # defaults to nim-libp2p version
peerStoreCapacity = none(int), # defaults to 1.25 maxConnections
# TODO: make this argument required after tests are updated
rng: ref HmacDrbgContext = crypto.newRng()
): T {.raises: [Defect, LPError, IOError, TLSStreamProtocolError].} =
## Creates a Waku Node instance.
info "Initializing networking", addrs=netConfig.announcedAddresses
let switch = newWakuSwitch( let switch = newWakuSwitch(
some(nodekey), some(nodekey),
hostAddress, address = netConfig.hostAddress,
wsHostAddress, wsAddress = netConfig.wsHostAddress,
transportFlags = {ServerFlags.ReuseAddr}, transportFlags = {ServerFlags.ReuseAddr},
rng = rng, rng = rng,
maxConnections = maxConnections, maxConnections = maxConnections,
wssEnabled = wssEnabled, wssEnabled = netConfig.wssEnabled,
secureKeyPath = secureKey, secureKeyPath = secureKey,
secureCertPath = secureCert, secureCertPath = secureCert,
nameResolver = nameResolver, nameResolver = nameResolver,
sendSignedPeerRecord = sendSignedPeerRecord, sendSignedPeerRecord = sendSignedPeerRecord,
agentString = agentString, agentString = agentString,
peerStoreCapacity = peerStoreCapacity, peerStoreCapacity = peerStoreCapacity,
services = @[Service(autonatservice)], services = @[Service(getAutonatService(rng))],
) )
let wakuNode = WakuNode( return WakuNode(
peerManager: PeerManager.new(switch, peerStorage), peerManager: PeerManager.new(switch, peerStorage),
switch: switch, switch: switch,
rng: rng, rng: rng,
enr: enr, enr: netConfig.getEnr(wakuDiscv5, nodekey),
announcedAddresses: announcedAddresses announcedAddresses: netConfig.announcedAddresses,
wakuDiscv5: if wakuDiscV5.isSome(): wakuDiscV5.get() else: nil,
) )
return wakuNode
proc peerInfo*(node: WakuNode): PeerInfo = proc peerInfo*(node: WakuNode): PeerInfo =
node.switch.peerInfo node.switch.peerInfo

View File

@ -40,7 +40,7 @@ proc init*(
addrs: addrs, addrs: addrs,
enr: enr, enr: enr,
protocols: protocols) protocols: protocols)
return remotePeerInfo return remotePeerInfo
proc init*(p: typedesc[RemotePeerInfo], proc init*(p: typedesc[RemotePeerInfo],
@ -49,7 +49,7 @@ proc init*(p: typedesc[RemotePeerInfo],
enr: Option[enr.Record] = none(enr.Record), enr: Option[enr.Record] = none(enr.Record),
protocols: seq[string] = @[]): RemotePeerInfo protocols: seq[string] = @[]): RemotePeerInfo
{.raises: [Defect, ResultError[cstring], LPError].} = {.raises: [Defect, ResultError[cstring], LPError].} =
let remotePeerInfo = RemotePeerInfo( let remotePeerInfo = RemotePeerInfo(
peerId: PeerID.init(peerId).tryGet(), peerId: PeerID.init(peerId).tryGet(),
addrs: addrs, addrs: addrs,
@ -97,7 +97,7 @@ proc parseRemotePeerInfo*(address: string): RemotePeerInfo {.raises: [Defect, Va
# nim-libp2p dialing requires remote peers to be initialised with a peerId and a wire address # nim-libp2p dialing requires remote peers to be initialised with a peerId and a wire address
let let
peerIdStr = p2pPart.toString()[].split("/")[^1] peerIdStr = p2pPart.toString()[].split("/")[^1]
wireAddr = nwPart & tcpPart & wsPart & wssPart wireAddr = nwPart & tcpPart & wsPart & wssPart
if (not wireAddr.validWireAddr()): if (not wireAddr.validWireAddr()):
@ -111,12 +111,12 @@ proc toRemotePeerInfo*(enr: enr.Record): Result[RemotePeerInfo, cstring] =
if not typedR.secp256k1.isSome: if not typedR.secp256k1.isSome:
return err("enr: no secp256k1 key in record") return err("enr: no secp256k1 key in record")
let let
pubKey = ? keys.PublicKey.fromRaw(typedR.secp256k1.get) pubKey = ? keys.PublicKey.fromRaw(typedR.secp256k1.get)
peerId = ? PeerID.init(crypto.PublicKey(scheme: Secp256k1, peerId = ? PeerID.init(crypto.PublicKey(scheme: Secp256k1,
skkey: secp.SkPublicKey(pubKey))) skkey: secp.SkPublicKey(pubKey)))
var addrs = newSeq[MultiAddress]() var addrs = newSeq[MultiAddress]()
let transportProto = getTransportProtocol(typedR) let transportProto = getTransportProtocol(typedR)
@ -182,3 +182,18 @@ proc hasProtocol*(ma: MultiAddress, proto: string): bool =
if p == MultiCodec.codec(proto): if p == MultiCodec.codec(proto):
return true return true
return false return false
func hasUdpPort*(peer: RemotePeerInfo): bool =
if peer.enr.isNone():
return false
let
enr = peer.enr.get()
typedEnrRes = enr.toTypedRecord()
if typedEnrRes.isErr():
return false
let typedEnr = typedEnrRes.get()
typedEnr.udp.isSome() or typedEnr.udp6.isSome()

View File

@ -34,21 +34,25 @@ type
Filter = 2, Filter = 2,
Lightpush = 3, Lightpush = 3,
func toFieldPair(multiaddrs: seq[MultiAddress]): FieldPair = func getRawField*(multiaddrs: seq[MultiAddress]): seq[byte] =
## Converts a seq of multiaddrs to a `multiaddrs` ENR
## field pair according to https://rfc.vac.dev/spec/31/
var fieldRaw: seq[byte] var fieldRaw: seq[byte]
for multiaddr in multiaddrs: for multiaddr in multiaddrs:
let let
maRaw = multiaddr.data.buffer # binary encoded multiaddr maRaw = multiaddr.data.buffer # binary encoded multiaddr
maSize = maRaw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer maSize = maRaw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer
assert maSize.len == 2 assert maSize.len == 2
fieldRaw.add(concat(@maSize, maRaw)) fieldRaw.add(concat(@maSize, maRaw))
return fieldRaw
func toFieldPair*(multiaddrs: seq[MultiAddress]): FieldPair =
## Converts a seq of multiaddrs to a `multiaddrs` ENR
## field pair according to https://rfc.vac.dev/spec/31/
let fieldRaw = multiaddrs.getRawField()
return toFieldPair(MULTIADDR_ENR_FIELD, fieldRaw) return toFieldPair(MULTIADDR_ENR_FIELD, fieldRaw)
func stripPeerId(multiaddr: MultiAddress): MultiAddress = func stripPeerId(multiaddr: MultiAddress): MultiAddress =
@ -58,10 +62,10 @@ func stripPeerId(multiaddr: MultiAddress): MultiAddress =
if item[].protoName()[] != "p2p": if item[].protoName()[] != "p2p":
# Add all parts except p2p peerId # Add all parts except p2p peerId
discard cleanAddr.append(item[]) discard cleanAddr.append(item[])
return cleanAddr return cleanAddr
func stripPeerIds(multiaddrs: seq[MultiAddress]): seq[MultiAddress] = func stripPeerIds*(multiaddrs: seq[MultiAddress]): seq[MultiAddress] =
var cleanAddrs: seq[MultiAddress] var cleanAddrs: seq[MultiAddress]
for multiaddr in multiaddrs: for multiaddr in multiaddrs:
@ -69,19 +73,19 @@ func stripPeerIds(multiaddrs: seq[MultiAddress]): seq[MultiAddress] =
cleanAddrs.add(multiaddr.stripPeerId()) cleanAddrs.add(multiaddr.stripPeerId())
else: else:
cleanAddrs.add(multiaddr) cleanAddrs.add(multiaddr)
return cleanAddrs return cleanAddrs
func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] = func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] =
## Attempts to read `numBytes` from a sequence, from ## Attempts to read `numBytes` from a sequence, from
## position `pos`. Returns the requested slice or ## position `pos`. Returns the requested slice or
## an error if `rawBytes` boundary is exceeded. ## an error if `rawBytes` boundary is exceeded.
## ##
## If successful, `pos` is advanced by `numBytes` ## If successful, `pos` is advanced by `numBytes`
if rawBytes[pos..^1].len() < numBytes: if rawBytes[pos..^1].len() < numBytes:
return err("Exceeds maximum available bytes") return err("Exceeds maximum available bytes")
let slicedSeq = rawBytes[pos..<pos+numBytes] let slicedSeq = rawBytes[pos..<pos+numBytes]
pos += numBytes pos += numBytes
@ -101,8 +105,8 @@ func initWakuFlags*(lightpush, filter, store, relay: bool): WakuEnrBitfield =
# TODO: With the changes in this PR, this can be refactored? Using the enum? # TODO: With the changes in this PR, this can be refactored? Using the enum?
# Perhaps refactor to: # Perhaps refactor to:
# WaKuEnr.initEnr(..., capabilities=[Store, Lightpush]) # WaKuEnr.enr.Record.init(..., capabilities=[Store, Lightpush])
# WaKuEnr.initEnr(..., capabilities=[Store, Lightpush, Relay, Filter]) # WaKuEnr.enr.Record.init(..., capabilities=[Store, Lightpush, Relay, Filter])
# Safer also since we dont inject WakuEnrBitfield, and we let this package # Safer also since we dont inject WakuEnrBitfield, and we let this package
# handle the bits according to the capabilities # handle the bits according to the capabilities
@ -141,12 +145,13 @@ func toMultiAddresses*(multiaddrsField: seq[byte]): seq[MultiAddress] =
return multiaddrs return multiaddrs
func initEnr*(privateKey: crypto.PrivateKey, func init*(T: type enr.Record,
enrIp: Option[ValidIpAddress], privateKey: crypto.PrivateKey,
enrTcpPort, enrUdpPort: Option[Port], enrIp: Option[ValidIpAddress],
wakuFlags = none(WakuEnrBitfield), enrTcpPort, enrUdpPort: Option[Port],
multiaddrs: seq[MultiAddress] = @[]): enr.Record = wakuFlags = none(WakuEnrBitfield),
multiaddrs: seq[MultiAddress] = @[]): T =
assert privateKey.scheme == PKScheme.Secp256k1 assert privateKey.scheme == PKScheme.Secp256k1
## Waku-specific ENR fields (https://rfc.vac.dev/spec/31/) ## Waku-specific ENR fields (https://rfc.vac.dev/spec/31/)
@ -166,14 +171,14 @@ func initEnr*(privateKey: crypto.PrivateKey,
enr = enr.Record.init(1, pk, enr = enr.Record.init(1, pk,
enrIp, enrTcpPort, enrUdpPort, enrIp, enrTcpPort, enrUdpPort,
wakuEnrFields).expect("Record within size limits") wakuEnrFields).expect("Record within size limits")
return enr return enr
proc supportsCapability*(r: Record, capability: Capabilities): bool = proc supportsCapability*(r: Record, capability: Capabilities): bool =
let enrCapabilities = r.get(WAKU_ENR_FIELD, seq[byte]) let enrCapabilities = r.get(WAKU_ENR_FIELD, seq[byte])
if enrCapabilities.isOk(): if enrCapabilities.isOk():
return testBit(enrCapabilities.get()[0], capability.ord) return testBit(enrCapabilities.get()[0], capability.ord)
return false return false
proc getCapabilities*(r: Record): seq[Capabilities] = proc getCapabilities*(r: Record): seq[Capabilities] =
return toSeq(Capabilities.low..Capabilities.high).filterIt(r.supportsCapability(it)) return toSeq(Capabilities.low..Capabilities.high).filterIt(r.supportsCapability(it))