feat(discv5): added find random nodes with predicate (#1762) (#1763)

* feat(discv5): added find random nodes with predicate (#1762)

Co-authored-by: Lorenzo Delgado <lnsdev@proton.me>

* chore: play around with sleep times on tests

* chore: simplify flaky tests

* chore: update waku/v2/node/waku_node.nim

Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com>

* chore: update tests/v2/test_waku_discv5.nim

Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com>

* chore: update examples/v2/subscriber.nim

Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com>

---------

Co-authored-by: Lorenzo Delgado <lnsdev@proton.me>
Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com>
This commit is contained in:
Hanno Cornelius 2023-06-06 16:36:20 +02:00 committed by GitHub
parent a9505892a5
commit 21737c7c1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 402 additions and 311 deletions

View File

@ -652,13 +652,11 @@ proc startNode(node: WakuNode, conf: WakuNodeConf,
except CatchableError: except CatchableError:
return err("failed to start waku node: " & getCurrentExceptionMsg()) return err("failed to start waku node: " & getCurrentExceptionMsg())
# Start discv5 and connect to discovered nodes # Start discv5 based discovery service (discovery loop)
if conf.discv5Discovery: if conf.discv5Discovery:
try: let startDiscv5Res = await node.startDiscv5()
if not await node.startDiscv5(): if startDiscv5Res.isErr():
error "could not start Discovery v5" return err("failed to start waku discovery v5: " & startDiscv5Res.error)
except CatchableError:
return err("failed to start waku discovery v5: " & getCurrentExceptionMsg())
# Connect to configured static nodes # Connect to configured static nodes
if conf.staticnodes.len > 0: if conf.staticnodes.len > 0:

View File

@ -66,8 +66,10 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
await node.start() await node.start()
await node.mountRelay() await node.mountRelay()
node.peerManager.start() node.peerManager.start()
if not await node.startDiscv5():
error "failed to start discv5" let discv5Res = await node.startDiscv5()
if discv5Res.isErr():
error "failed to start discv5", error= discv5Res.error
quit(1) quit(1)
# wait for a minimum of peers to be connected, otherwise messages wont be gossiped # wait for a minimum of peers to be connected, otherwise messages wont be gossiped

View File

@ -61,8 +61,10 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
await node.start() await node.start()
await node.mountRelay() await node.mountRelay()
node.peerManager.start() node.peerManager.start()
if not await node.startDiscv5():
error "failed to start discv5" let discv5Res = await node.startDiscv5()
if discv5Res.isErr():
error "failed to start discv5", error = discv5Res.error
quit(1) quit(1)
# wait for a minimum of peers to be connected, otherwise messages wont be gossiped # wait for a minimum of peers to be connected, otherwise messages wont be gossiped

View File

@ -1,201 +1,282 @@
{.used.} {.used.}
import import
stew/[results, byteutils], std/[sequtils, sets],
stew/results,
stew/shims/net, stew/shims/net,
chronos, chronos,
chronicles, chronicles,
testutils/unittests, testutils/unittests,
libp2p/crypto/crypto, libp2p/crypto/crypto as libp2p_keys,
eth/keys, eth/keys as eth_keys
eth/p2p/discoveryv5/enr
import import
../../waku/v2/waku_node, ../../waku/v2/waku_node,
../../waku/v2/waku_core, ../../waku/v2/waku_enr,
../../waku/v2/waku_discv5, ../../waku/v2/waku_discv5,
./testlib/common, ./testlib/common,
./testlib/wakucore, ./testlib/wakucore,
./testlib/wakunode ./testlib/wakunode
proc newTestEnrRecord(privKey: libp2p_keys.PrivateKey,
extIp: string, tcpPort: uint16, udpPort: uint16,
flags = none(CapabilitiesBitfield)): waku_enr.Record =
var builder = EnrBuilder.init(privKey)
builder.withIpAddressAndPorts(
ipAddr = some(ValidIpAddress.init(extIp)),
tcpPort = some(Port(tcpPort)),
udpPort = some(Port(udpPort)),
)
if flags.isSome():
builder.withWakuCapabilities(flags.get())
builder.build().tryGet()
proc newTestDiscv5Node(privKey: libp2p_keys.PrivateKey,
bindIp: string, tcpPort: uint16, udpPort: uint16,
record: waku_enr.Record,
bootstrapRecords = newSeq[waku_enr.Record]()): WakuNode =
let config = WakuDiscoveryV5Config(
privateKey: eth_keys.PrivateKey(privKey.skkey),
address: ValidIpAddress.init(bindIp),
port: Port(udpPort),
bootstrapRecords: bootstrapRecords,
)
let protocol = WakuDiscoveryV5.new(rng(), config, some(record))
let node = newTestWakuNode(
nodeKey = privKey,
bindIp = ValidIpAddress.init(bindIp),
bindPort = Port(tcpPort),
wakuDiscv5 = some(protocol)
)
return node
procSuite "Waku Discovery v5": procSuite "Waku Discovery v5":
asyncTest "Waku Discovery v5 end-to-end": asyncTest "find random peers":
## Tests integrated discovery v5 ## Given
# Node 1
let let
bindIp = ValidIpAddress.init("0.0.0.0") privKey1 = generateSecp256k1Key()
extIp = ValidIpAddress.init("127.0.0.1") bindIp1 = "0.0.0.0"
extIp1 = "127.0.0.1"
tcpPort1 = 61500u16
udpPort1 = 9000u16
nodeKey1 = generateSecp256k1Key() let record1 = newTestEnrRecord(
nodeTcpPort1 = Port(61500) privKey = privKey1,
nodeUdpPort1 = Port(9000) extIp = extIp1,
node1 = newTestWakuNode(nodeKey1, bindIp, nodeTcpPort1) tcpPort = tcpPort1,
udpPort = udpPort1,
nodeKey2 = generateSecp256k1Key() )
nodeTcpPort2 = Port(61502) let node1 = newTestDiscv5Node(
nodeUdpPort2 = Port(9002) privKey = privKey1,
node2 = newTestWakuNode(nodeKey2, bindIp, nodeTcpPort2) bindIp = bindIp1,
tcpPort = tcpPort1,
nodeKey3 = generateSecp256k1Key() udpPort = udpPort1,
nodeTcpPort3 = Port(61504) record = record1
nodeUdpPort3 = Port(9004)
node3 = newTestWakuNode(nodeKey3, bindIp, nodeTcpPort3)
flags = CapabilitiesBitfield.init(
lightpush = false,
filter = false,
store = false,
relay = true
) )
# E2E relay test paramaters # Node 2
pubSubTopic = "/waku/2/default-waku/proto"
contentTopic = ContentTopic("/waku/2/default-content/proto")
payload = "Can you see me?".toBytes()
message = WakuMessage(payload: payload, contentTopic: contentTopic)
# Mount discv5
node1.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort1), some(nodeUdpPort1),
bindIp,
nodeUdpPort1,
newSeq[enr.Record](),
false,
keys.PrivateKey(nodeKey1.skkey),
flags,
newSeq[MultiAddress](), # Empty multiaddr fields, for now
node1.rng
)
node2.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort2), some(nodeUdpPort2),
bindIp,
nodeUdpPort2,
@[node1.wakuDiscv5.protocol.localNode.record], # Bootstrap with node1
false,
keys.PrivateKey(nodeKey2.skkey),
flags,
newSeq[MultiAddress](), # Empty multiaddr fields, for now
node2.rng
)
node3.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort3), some(nodeUdpPort3),
bindIp,
nodeUdpPort3,
@[node2.wakuDiscv5.protocol.localNode.record], # Bootstrap with node2
false,
keys.PrivateKey(nodeKey3.skkey),
flags,
newSeq[MultiAddress](), # Empty multiaddr fields, for now
node3.rng
)
await node1.mountRelay()
await node2.mountRelay()
await node3.mountRelay()
await allFutures([node1.start(), node2.start(), node3.start()])
await allFutures([node1.startDiscv5(), node2.startDiscv5(), node3.startDiscv5()])
await sleepAsync(3000.millis) # Give the algorithm some time to work its magic
check:
node1.wakuDiscv5.protocol.nodesDiscovered > 0
node2.wakuDiscv5.protocol.nodesDiscovered > 0
node3.wakuDiscv5.protocol.nodesDiscovered > 0
# Let's see if we can deliver a message end-to-end
# var completionFut = newFuture[bool]()
# proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} =
# let msg = WakuMessage.decode(data)
# if msg.isOk():
# let val = msg.value()
# check:
# topic == pubSubTopic
# val.contentTopic == contentTopic
# val.payload == payload
# completionFut.complete(true)
# node3.subscribe(pubSubTopic, relayHandler)
# await sleepAsync(2000.millis)
# await node1.publish(pubSubTopic, message)
# check:
# (await completionFut.withTimeout(6.seconds)) == true
await allFutures([node1.stop(), node2.stop(), node3.stop()])
asyncTest "Custom multiaddresses are advertised correctly":
let let
bindIp = ValidIpAddress.init("0.0.0.0") privKey2 = generateSecp256k1Key()
extIp = ValidIpAddress.init("127.0.0.1") bindIp2 = "0.0.0.0"
expectedMultiAddr = MultiAddress.init("/ip4/200.200.200.200/tcp/9000/wss").tryGet() extIp2 = "127.0.0.1"
tcpPort2 = 61502u16
udpPort2 = 9002u16
flags = CapabilitiesBitfield.init( let record2 = newTestEnrRecord(
lightpush = false, privKey = privKey2,
filter = false, extIp = extIp2,
store = false, tcpPort = tcpPort2,
relay = true udpPort = udpPort2,
) )
nodeTcpPort1 = Port(9010) let node2 = newTestDiscv5Node(
nodeUdpPort1 = Port(9012) privKey = privKey2,
node1Key = generateSecp256k1Key() bindIp = bindIp2,
node1NetConfig = NetConfig.init(bindIp = bindIp, tcpPort = tcpPort2,
extIp = some(extIp), udpPort = udpPort2,
extPort = some(nodeTcpPort1), record = record2,
bindPort = nodeTcpPort1, )
extmultiAddrs = @[expectedMultiAddr],
wakuFlags = some(flags),
discv5UdpPort = some(nodeUdpPort1)).get()
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)
# Node 3
let
privKey3 = generateSecp256k1Key()
bindIp3 = "0.0.0.0"
extIp3 = "127.0.0.1"
tcpPort3 = 61504u16
udpPort3 = 9004u16
nodeTcpPort2 = Port(9014) let record3 = newTestEnrRecord(
nodeUdpPort2 = Port(9016) privKey = privKey3,
node2Key = generateSecp256k1Key() extIp = extIp3,
node2NetConfig = NetConfig.init(bindIp = bindIp, tcpPort = tcpPort3,
extIp = some(extIp), udpPort = udpPort3,
extPort = some(nodeTcpPort2), )
bindPort = nodeTcpPort2,
wakuFlags = some(flags),
discv5UdpPort = some(nodeUdpPort2)).get()
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()]) let node3 = newTestDiscv5Node(
privKey = privKey3,
bindIp = bindIp3,
tcpPort = tcpPort3,
udpPort = udpPort3,
record = record3,
bootstrapRecords = @[record1, record2]
)
await allFutures([node1.startDiscv5(), node2.startDiscv5()]) await allFutures(node1.start(), node2.start(), node3.start())
await sleepAsync(3000.millis) # Give the algorithm some time to work its magic ## When
# Starting discv5 via `WakuNode.startDiscV5()` starts the discv5 background task.
await allFutures(node1.startDiscv5(), node2.startDiscv5(), node3.startDiscv5())
let node1Enr = node2.wakuDiscv5.protocol.routingTable.buckets[0].nodes[0].record await sleepAsync(5.seconds) # Wait for discv5 discovery loop to run
let multiaddrs = node1Enr.toTyped().get().multiaddrs.get() let res = await node1.wakuDiscv5.findRandomPeers()
## Then
check: check:
node1.wakuDiscv5.protocol.nodesDiscovered > 0 res.len >= 1
node2.wakuDiscv5.protocol.nodesDiscovered > 0
multiaddrs.contains(expectedMultiAddr)
## Cleanup
await allFutures(node1.stop(), node2.stop(), node3.stop())
asyncTest "find random peers with predicate":
## Setup
# Records
let
privKey1 = generateSecp256k1Key()
bindIp1 = "0.0.0.0"
extIp1 = "127.0.0.1"
tcpPort1 = 61500u16
udpPort1 = 9000u16
let record1 = newTestEnrRecord(
privKey = privKey1,
extIp = extIp1,
tcpPort = tcpPort1,
udpPort = udpPort1,
flags = some(CapabilitiesBitfield.init(Capabilities.Relay))
)
let
privKey2 = generateSecp256k1Key()
bindIp2 = "0.0.0.0"
extIp2 = "127.0.0.1"
tcpPort2 = 61502u16
udpPort2 = 9002u16
let record2 = newTestEnrRecord(
privKey = privKey2,
extIp = extIp2,
tcpPort = tcpPort2,
udpPort = udpPort2,
flags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store))
)
let
privKey3 = generateSecp256k1Key()
bindIp3 = "0.0.0.0"
extIp3 = "127.0.0.1"
tcpPort3 = 61504u16
udpPort3 = 9004u16
let record3 = newTestEnrRecord(
privKey = privKey3,
extIp = extIp3,
tcpPort = tcpPort3,
udpPort = udpPort3,
flags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Filter))
)
let
privKey4 = generateSecp256k1Key()
bindIp4 = "0.0.0.0"
extIp4 = "127.0.0.1"
tcpPort4 = 61506u16
udpPort4 = 9006u16
let record4 = newTestEnrRecord(
privKey = privKey4,
extIp = extIp4,
tcpPort = tcpPort4,
udpPort = udpPort4,
flags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store))
)
# Nodes
let node1 = newTestDiscv5Node(
privKey = privKey1,
bindIp = bindIp1,
tcpPort = tcpPort1,
udpPort = udpPort1,
record = record1,
bootstrapRecords = @[record2]
)
let node2 = newTestDiscv5Node(
privKey = privKey2,
bindIp = bindIp2,
tcpPort = tcpPort2,
udpPort = udpPort2,
record = record2,
bootstrapRecords = @[record3, record4]
)
let node3 = newTestDiscv5Node(
privKey = privKey3,
bindIp = bindIp3,
tcpPort = tcpPort3,
udpPort = udpPort3,
record = record3
)
let node4 = newTestDiscv5Node(
privKey = privKey4,
bindIp = bindIp4,
tcpPort = tcpPort4,
udpPort = udpPort4,
record = record4
)
# Start nodes' discoveryV5 protocols
require node1.wakuDiscV5.start().isOk()
require node2.wakuDiscV5.start().isOk()
require node3.wakuDiscV5.start().isOk()
require node4.wakuDiscV5.start().isOk()
await allFutures(node1.start(), node2.start(), node3.start(), node4.start())
## Given
let recordPredicate = proc(record: waku_enr.Record): bool =
let typedRecord = record.toTyped()
if typedRecord.isErr():
return false
let capabilities = typedRecord.value.waku2
if capabilities.isNone():
return false
return capabilities.get().supportsCapability(Capabilities.Store)
## When
# # Do a random peer search with a predicate multiple times
# var peers = initHashSet[waku_enr.Record]()
# for i in 0..<10:
# for peer in await node1.wakuDiscv5.findRandomPeers(pred=recordPredicate):
# peers.incl(peer)
await sleepAsync(5.seconds) # Wait for discv5 discvery loop to run
let peers = await node1.wakuDiscv5.findRandomPeers(pred=recordPredicate)
## Then
check:
peers.len >= 1
peers.allIt(it.supportsCapability(Capabilities.Store))
# Cleanup
await allFutures(node1.stop(), node2.stop(), node3.stop(), node4.stop())

View File

@ -126,8 +126,8 @@ procSuite "Waku Peer Exchange":
) )
## Given ## Given
await allFutures([node1.start(), node2.start(), node3.start()]) await allFutures(node1.start(), node2.start(), node3.start())
await allFutures([node1.startDiscv5(), node2.startDiscv5()]) await allFutures(node1.startDiscv5(), node2.startDiscv5())
var attempts = 10 var attempts = 10
while (node1.wakuDiscv5.protocol.nodesDiscovered < 1 or while (node1.wakuDiscv5.protocol.nodesDiscovered < 1 or

View File

@ -856,59 +856,48 @@ proc startKeepalive*(node: WakuNode) =
asyncSpawn node.keepaliveLoop(defaultKeepalive) asyncSpawn node.keepaliveLoop(defaultKeepalive)
proc runDiscv5Loop(node: WakuNode) {.async.} = proc runDiscv5Loop(node: WakuNode) {.async.} =
## Continuously add newly discovered nodes ## Continuously add newly discovered nodes using Node Discovery v5
## using Node Discovery v5 if node.wakuDiscv5.isNil():
if (node.wakuDiscv5.isNil):
warn "Trying to run discovery v5 while it's disabled" warn "Trying to run discovery v5 while it's disabled"
return return
info "Starting discovery loop" info "starting discv5 discovery loop"
while node.wakuDiscv5.listening: while node.wakuDiscv5.listening:
trace "Running discovery loop" trace "running discv5 discovery loop"
let discoveredPeersRes = await node.wakuDiscv5.findRandomPeers() let discoveredRecords = await node.wakuDiscv5.findRandomPeers()
let discoveredPeers = discoveredRecords.mapIt(it.toRemotePeerInfo()).filterIt(it.isOk()).mapIt(it.value)
if discoveredPeersRes.isOk:
let discoveredPeers = discoveredPeersRes.get
let newSeen = discoveredPeers.countIt(not node.peerManager.peerStore[AddressBook].contains(it.peerId))
info "Discovered peers", discovered=discoveredPeers.len, new=newSeen
# Add all peers, new ones and already seen (in case their addresses changed)
for peer in discoveredPeers: for peer in discoveredPeers:
node.peerManager.addPeer(peer, Discv5) let isNew = not node.peerManager.peerStore[AddressBook].contains(peer.peerId)
if isNew:
debug "new peer discovered", peer= $peer, origin= "discv5"
node.peerManager.addPeer(peer, PeerOrigin.Discv5)
# Discovery `queryRandom` can have a synchronous fast path for example # Discovery `queryRandom` can have a synchronous fast path for example
# when no peers are in the routing table. Don't run it in continuous loop. # when no peers are in the routing table. Don't run it in continuous loop.
# #
# Also, give some time to dial the discovered nodes and update stats etc # Also, give some time to dial the discovered nodes and update stats, etc.
await sleepAsync(5.seconds) await sleepAsync(5.seconds)
proc startDiscv5*(node: WakuNode): Future[bool] {.async.} = proc startDiscv5*(node: WakuNode): Future[Result[void, string]] {.async.} =
## Start Discovery v5 service ## Start Discovery v5 service
if node.wakuDiscv5.isNil():
return err("discovery v5 is disabled")
info "Starting discovery v5 service" info "Starting discovery v5 service"
let res = node.wakuDiscv5.start()
if res.isErr():
return err("error in startDiscv5: " & res.error)
if not node.wakuDiscv5.isNil():
## First start listening on configured port
try:
trace "Start listening on discv5 port"
node.wakuDiscv5.open()
except CatchableError:
error "Failed to start discovery service. UDP port may be already in use"
return false
## Start Discovery v5
trace "Start discv5 service"
node.wakuDiscv5.start()
trace "Start discovering new peers using discv5" trace "Start discovering new peers using discv5"
asyncSpawn node.runDiscv5Loop() asyncSpawn node.runDiscv5Loop()
debug "Successfully started discovery v5 service" debug "Successfully started discovery v5 service"
info "Discv5: discoverable ENR ", enr = node.wakuDiscV5.protocol.localNode.record.toUri() info "Discv5: discoverable ENR ", enr = node.wakuDiscV5.protocol.localNode.record.toUri()
return true return ok()
return false
proc stopDiscv5*(node: WakuNode): Future[bool] {.async.} = proc stopDiscv5*(node: WakuNode): Future[bool] {.async.} =
## Stop Discovery v5 service ## Stop Discovery v5 service

View File

@ -4,15 +4,14 @@ else:
{.push raises: [].} {.push raises: [].}
import import
std/[strutils, options], std/[sequtils, strutils, options],
stew/results, stew/results,
stew/shims/net, stew/shims/net,
chronos, chronos,
chronicles, chronicles,
metrics, metrics,
libp2p/multiaddress, libp2p/multiaddress,
eth/keys, eth/keys as eth_keys,
eth/p2p/discoveryv5/enr,
eth/p2p/discoveryv5/node, eth/p2p/discoveryv5/node,
eth/p2p/discoveryv5/protocol eth/p2p/discoveryv5/protocol
import import
@ -29,14 +28,122 @@ logScope:
topics = "waku discv5" topics = "waku discv5"
## Config
type WakuDiscoveryV5Config* = object
discv5Config*: Option[DiscoveryConfig]
address*: ValidIpAddress
port*: Port
privateKey*: eth_keys.PrivateKey
bootstrapRecords*: seq[waku_enr.Record]
autoupdateRecord*: bool
## Protocol
type WakuDiscv5Predicate* = proc(record: waku_enr.Record): bool {.closure, gcsafe.}
type WakuDiscoveryV5* = ref object type WakuDiscoveryV5* = ref object
conf: WakuDiscoveryV5Config
protocol*: protocol.Protocol protocol*: protocol.Protocol
listening*: bool listening*: bool
proc new*(T: type WakuDiscoveryV5, rng: ref HmacDrbgContext, conf: WakuDiscoveryV5Config, record: Option[waku_enr.Record]): T =
let protocol = newProtocol(
rng = rng,
config = conf.discv5Config.get(protocol.defaultDiscoveryConfig),
bindPort = conf.port,
bindIp = conf.address,
privKey = conf.privateKey,
bootstrapRecords = conf.bootstrapRecords,
enrAutoUpdate = conf.autoupdateRecord,
previousRecord = record,
enrIp = none(ValidIpAddress),
enrTcpPort = none(Port),
enrUdpPort = none(Port),
)
#################### WakuDiscoveryV5(conf: conf, protocol: protocol, listening: false)
# Helper functions #
#################### proc new*(T: type WakuDiscoveryV5,
extIp: Option[ValidIpAddress],
extTcpPort: Option[Port],
extUdpPort: Option[Port],
bindIP: ValidIpAddress,
discv5UdpPort: Port,
bootstrapEnrs = newSeq[enr.Record](),
enrAutoUpdate = false,
privateKey: eth_keys.PrivateKey,
flags: CapabilitiesBitfield,
multiaddrs = newSeq[MultiAddress](),
rng: ref HmacDrbgContext,
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T {.
deprecated: "use the config and record proc variant instead".}=
let record = block:
var builder = EnrBuilder.init(privateKey)
builder.withIpAddressAndPorts(
ipAddr = extIp,
tcpPort = extTcpPort,
udpPort = extUdpPort,
)
builder.withWakuCapabilities(flags)
builder.withMultiaddrs(multiaddrs)
builder.build().expect("Record within size limits")
let conf = WakuDiscoveryV5Config(
discv5Config: some(discv5Config),
address: bindIP,
port: discv5UdpPort,
privateKey: privateKey,
bootstrapRecords: bootstrapEnrs,
autoupdateRecord: enrAutoUpdate,
)
WakuDiscoveryV5.new(rng, conf, some(record))
proc start*(wd: WakuDiscoveryV5): Result[void, string] =
if wd.listening:
return err("already listening")
# Start listening on configured port
debug "start listening on udp port", address = $wd.conf.address, port = $wd.conf.port
try:
wd.protocol.open()
except CatchableError:
return err("failed to open udp port: " & getCurrentExceptionMsg())
wd.listening = true
# Start Discovery v5
trace "start discv5 service"
wd.protocol.start()
ok()
proc closeWait*(wd: WakuDiscoveryV5) {.async.} =
debug "closing Waku discovery v5 node"
if not wd.listening:
return
wd.listening = false
await wd.protocol.closeWait()
proc findRandomPeers*(wd: WakuDiscoveryV5, pred: WakuDiscv5Predicate = nil): Future[seq[waku_enr.Record]] {.async.} =
## Find random peers to connect to using Discovery v5
let discoveredNodes = await wd.protocol.queryRandom()
var discoveredRecords = discoveredNodes.mapIt(it.record)
# Filter out nodes that do not match the predicate
if not pred.isNil():
discoveredRecords = discoveredRecords.filter(pred)
return discoveredRecords
## Helper functions
proc parseBootstrapAddress(address: string): Result[enr.Record, cstring] = proc parseBootstrapAddress(address: string): Result[enr.Record, cstring] =
logScope: logScope:
@ -71,91 +178,3 @@ proc addBootstrapNode*(bootstrapAddr: string,
return return
bootstrapEnrs.add(enrRes.value) bootstrapEnrs.add(enrRes.value)
####################
# Discovery v5 API #
####################
proc new*(T: type WakuDiscoveryV5,
extIp: Option[ValidIpAddress],
extTcpPort: Option[Port],
extUdpPort: Option[Port],
bindIP: ValidIpAddress,
discv5UdpPort: Port,
bootstrapEnrs = newSeq[enr.Record](),
enrAutoUpdate = false,
privateKey: keys.PrivateKey,
flags: CapabilitiesBitfield,
multiaddrs = newSeq[MultiAddress](),
rng: ref HmacDrbgContext,
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
# Add the waku capabilities field
var enrInitFields = @[(CapabilitiesEnrField, @[flags.byte])]
# Add the waku multiaddrs field
if multiaddrs.len > 0:
let value = waku_enr.encodeMultiaddrs(multiaddrs)
enrInitFields.add((MultiaddrEnrField, value))
let protocol = newProtocol(
privateKey,
enrIp = extIp,
enrTcpPort = extTcpPort,
enrUdpPort = extUdpPort,
enrInitFields,
bootstrapEnrs,
bindPort = discv5UdpPort,
bindIp = bindIP,
enrAutoUpdate = enrAutoUpdate,
config = discv5Config,
rng = rng
)
WakuDiscoveryV5(protocol: protocol, listening: false)
# TODO: Do not raise an exception, return a result
proc open*(wd: WakuDiscoveryV5) {.raises: [CatchableError].} =
debug "Opening Waku discovery v5 ports"
if wd.listening:
return
wd.protocol.open()
wd.listening = true
proc start*(wd: WakuDiscoveryV5) =
debug "starting Waku discovery v5 service"
wd.protocol.start()
proc closeWait*(wd: WakuDiscoveryV5) {.async.} =
debug "closing Waku discovery v5 node"
if not wd.listening:
return
wd.listening = false
await wd.protocol.closeWait()
proc findRandomPeers*(wd: WakuDiscoveryV5): Future[Result[seq[RemotePeerInfo], cstring]] {.async.} =
## Find random peers to connect to using Discovery v5
# Query for a random target and collect all discovered nodes
let discoveredNodes = await wd.protocol.queryRandom()
## Filter based on our needs
# let filteredNodes = discoveredNodes.filter(isWakuNode) # Currently only a single predicate
# TODO: consider node filtering based on ENR; we do not filter based on ENR in the first waku discv5 beta stage
var discoveredPeers: seq[RemotePeerInfo]
for node in discoveredNodes:
let res = node.record.toRemotePeerInfo()
if res.isErr():
error "failed to convert ENR to peer info", enr= $node.record, err=res.error
waku_discv5_errors.inc(labelValues = ["peer_info_failure"])
continue
discoveredPeers.add(res.value)
return ok(discoveredPeers)