mirror of https://github.com/waku-org/nwaku.git
* 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:
parent
a9505892a5
commit
21737c7c1b
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
Loading…
Reference in New Issue