Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: saving peers enr capabilities #3127

Merged
merged 16 commits into from
Oct 24, 2024
24 changes: 23 additions & 1 deletion tests/test_waku_enr.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import std/[options, sequtils], stew/results, testutils/unittests
import waku/waku_core, waku/waku_enr, ./testlib/wakucore
import waku/waku_core, waku/waku_enr, ./testlib/wakucore, waku/waku_core/codecs

suite "Waku ENR - Capabilities bitfield":
test "check capabilities support":
Expand Down Expand Up @@ -97,6 +97,28 @@ suite "Waku ENR - Capabilities bitfield":
bitfield.supportsCapability(Capabilities.Lightpush) == false
bitfield.toCapabilities() == @[Capabilities.Relay, Capabilities.Store]

test "get capabilities codecs from record":
## Given
let
enrSeqNum = 1u64
enrPrivKey = generatesecp256k1key()

## When
var builder = EnrBuilder.init(enrPrivKey, seqNum = enrSeqNum)
builder.withWakuCapabilities(Capabilities.Relay, Capabilities.Store)

let recordRes = builder.build()

## Then
assert recordRes.isOk(), $recordRes.error
let record = recordRes.tryGet()

let codecs = record.getCapabilitiesCodecs()
check:
codecs.len == 2
codecs.contains(WakuRelayCodec)
codecs.contains(WakuStoreCodec)

test "check capabilities on a non-waku node record":
## Given
# non waku enr, i.e. Ethereum one
Expand Down
40 changes: 38 additions & 2 deletions tests/waku_core/test_peers.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{.used.}

import
stew/results, testutils/unittests, libp2p/multiaddress, libp2p/peerid, libp2p/errors
import waku/waku_core
stew/results,
testutils/unittests,
libp2p/multiaddress,
libp2p/peerid,
libp2p/errors,
confutils/toml/std/net
import waku/[waku_core, waku_core/codecs, waku_enr], ../testlib/wakucore

suite "Waku Core - Peers":
test "Peer info parses correctly":
Expand Down Expand Up @@ -141,3 +146,34 @@ suite "Waku Core - Peers":
## Then
check:
parsePeerInfo(address).isErr()

test "ENRs capabilities are filled when creating RemotePeerInfo":
let
enrSeqNum = 1u64
enrPrivKey = generatesecp256k1key()

## When
var builder = EnrBuilder.init(enrPrivKey, seqNum = enrSeqNum)
builder.withIpAddressAndPorts(
ipAddr = some(parseIpAddress("127.0.0.1")),
tcpPort = some(Port(0)),
udpPort = some(Port(0)),
)
builder.withWakuCapabilities(Capabilities.Relay, Capabilities.Store)

let recordRes = builder.build()

## Then
assert recordRes.isOk(), $recordRes.error
let record = recordRes.tryGet()

let remotePeerInfoRes = record.toRemotePeerInfo()
assert remotePeerInfoRes.isOk(),
"failed creating RemotePeerInfo: " & $remotePeerInfoRes.error()

let remotePeerInfo = remotePeerInfoRes.get()

check:
remotePeerInfo.protocols.len == 2
remotePeerInfo.protocols.contains(WakuRelayCodec)
remotePeerInfo.protocols.contains(WakuStoreCodec)
102 changes: 73 additions & 29 deletions tests/waku_discv5/test_waku_discv5.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ import

import
waku/[waku_core/topics, waku_enr, discovery/waku_discv5, common/enr],
../testlib/[wakucore, testasync, assertions, futures],
../testlib/[wakucore, testasync, assertions, futures, wakunode],
../waku_enr/utils,
./utils

import eth/p2p/discoveryv5/enr as ethEnr

procSuite "Waku Discovery v5":
include waku/factory/waku

suite "Waku Discovery v5":
const validEnr =
"enr:-K64QGAvsATunmvMT5c3LFjKS0tG39zlQ1195Z2pWu6RoB5fWP3EXz9QPlRXN" &
"wOtDoRLgm4bATUB53AC8uml-ZtUE_kBgmlkgnY0gmlwhApkZgOKbXVsdGlhZGRyc4" &
"CCcnOTAAAIAAAAAQACAAMABAAFAAYAB4lzZWNwMjU2azGhAwG-CMmXpAPj84f6dCt" &
"MZ6xVYOa6bdmgAiKYG6LKGQlbg3RjcILqYIV3YWt1MgE"

let
rng = eth_keys.newRng()
pk1 = eth_keys.PrivateKey.random(rng[])
pk2 = eth_keys.PrivateKey.random(rng[])

enrNoCapabilities =
initRecord(1, pk1, {"rs": @[0.byte, 0.byte, 1.byte, 0.byte, 0.byte]}).value()
enrRelay = initRecord(
1, pk2, {"waku2": @[1.byte], "rs": @[0.byte, 1.byte, 1.byte, 0.byte, 1.byte]}
)
.value()
enrNoShardingInfo = initRecord(1, pk1, {"waku2": @[1.byte]}).value()

suite "shardingPredicate":
var
recordCluster21 {.threadvar.}: Record
Expand Down Expand Up @@ -120,6 +120,14 @@ procSuite "Waku Discovery v5":

asyncTest "filter peer per bootnode":
let
enrRelay = initRecord(
1,
pk2,
{"waku2": @[1.byte], "rs": @[0.byte, 1.byte, 1.byte, 0.byte, 1.byte]},
)
.value()
enrNoCapabilities =
initRecord(1, pk1, {"rs": @[0.byte, 0.byte, 1.byte, 0.byte, 0.byte]}).value()
predicateNoCapabilities =
shardingPredicate(enrNoCapabilities, @[enrNoCapabilities]).get()
predicateNoCapabilitiesWithBoth =
Expand Down Expand Up @@ -151,8 +159,10 @@ procSuite "Waku Discovery v5":
predicateRecord.isNone()

asyncTest "no relay sharding info":
let predicateNoShardingInfo =
shardingPredicate(enrNoShardingInfo, @[enrNoShardingInfo])
let
enrNoShardingInfo = initRecord(1, pk1, {"waku2": @[1.byte]}).value()
predicateNoShardingInfo =
shardingPredicate(enrNoShardingInfo, @[enrNoShardingInfo])

check:
predicateNoShardingInfo.isNone()
Expand All @@ -166,7 +176,7 @@ procSuite "Waku Discovery v5":
indices: seq[uint64] = @[],
recordFlags: Option[CapabilitiesBitfield] = none(CapabilitiesBitfield),
bootstrapRecords: seq[waku_enr.Record] = @[],
): (WakuDiscoveryV5, Record) =
): (WakuDiscoveryV5, Record) {.raises: [ValueError, LPError].} =
let
privKey = generateSecp256k1Key()
record = newTestEnrRecord(
Expand All @@ -188,17 +198,6 @@ procSuite "Waku Discovery v5":

(node, record)

let filterForStore: WakuDiscv5Predicate = 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)

asyncTest "find random peers without predicate":
# Given 3 nodes
let
Expand Down Expand Up @@ -234,6 +233,17 @@ procSuite "Waku Discovery v5":
await allFutures(node1.stop(), node2.stop(), node3.stop())

asyncTest "find random peers with parameter predicate":
let filterForStore: WakuDiscv5Predicate = 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)

# Given 4 nodes
let
(node3, record3) = buildNode(
Expand Down Expand Up @@ -346,11 +356,6 @@ procSuite "Waku Discovery v5":
await allFutures(node1.stop(), node2.stop(), node3.stop(), node4.stop())

suite "addBoostrapNode":
let validEnr =
"enr:-I-4QG3mX250ArniAs2DLpW-QHOLKSD5x_Ibp8AYcQZbz1HhHFJtl2dNDGcha" &
"U5ugLbDKRgtTDZH8NsxXlTXDpYAgzgBgmlkgnY0gnJzjwAVBgABAAIABQAHAAkAC4" &
"lzZWNwMjU2azGhA4_KwN0NRRmmfQ-B9B2h2PZjoJvBnaIOi6sR_b2UTQBBhXdha3U" & "yAQ"

asyncTest "address is valid":
# Given an empty list of enrs
var enrs: seq[Record] = @[]
Expand Down Expand Up @@ -400,3 +405,42 @@ procSuite "Waku Discovery v5":
# Then the enr is not added to the list
check:
enrs.len == 0

suite "waku discv5 initialization":
asyncTest "Discv5 bootstrap nodes should be added to the peer store":
var conf = defaultTestWakuNodeConf()

conf.discv5BootstrapNodes = @[validEnr]

let waku = Waku.init(conf).valueOr:
raiseAssert error

discard setupDiscoveryV5(
waku.node.enr, waku.node.peerManager, waku.node.topicSubscriptionQueue,
waku.conf, waku.dynamicBootstrapNodes, waku.rng, waku.key,
)

check:
waku.node.peerManager.wakuPeerStore.peers().anyIt(
it.enr.isSome() and it.enr.get().toUri() == validEnr
)

asyncTest "Invalid discv5 bootstrap node ENRs are ignored":
var conf = defaultTestWakuNodeConf()

let invalidEnr = "invalid-enr"

conf.discv5BootstrapNodes = @[invalidEnr]

let waku = Waku.init(conf).valueOr:
raiseAssert error

discard setupDiscoveryV5(
waku.node.enr, waku.node.peerManager, waku.node.topicSubscriptionQueue,
waku.conf, waku.dynamicBootstrapNodes, waku.rng, waku.key,
)

check:
not waku.node.peerManager.wakuPeerStore.peers().anyIt(
it.enr.isSome() and it.enr.get().toUri() == invalidEnr
)
8 changes: 8 additions & 0 deletions waku/discovery/waku_discv5.nim
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@ proc setupDiscoveryV5*(
for enrUri in conf.discv5BootstrapNodes:
addBootstrapNode(enrUri, discv5BootstrapEnrs)

for enr in discv5BootstrapEnrs:
let peerInfoRes = enr.toRemotePeerInfo()
if peerInfoRes.isOk():
nodePeerManager.addPeer(peerInfoRes.get(), PeerOrigin.Discv5)
else:
debug "could not convert discv5 bootstrap node to peerInfo, not adding peer to Peer Store",
enr = enr.toUri(), error = peerInfoRes.error

discv5BootstrapEnrs.add(dynamicBootstrapEnrs)

let discv5Config = DiscoveryConfig.init(
Expand Down
6 changes: 6 additions & 0 deletions waku/factory/node_factory.nim
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,18 @@ proc startNode*(

# Connect to configured static nodes
if conf.staticnodes.len > 0:
if not conf.relay:
return err("waku relay (--relay=true) should be set when configuring staticnodes")
try:
await connectToNodes(node, conf.staticnodes, "static")
except CatchableError:
return err("failed to connect to static nodes: " & getCurrentExceptionMsg())

if dynamicBootstrapNodes.len > 0:
if not conf.relay:
return err(
"waku relay (--relay=true) should be set when configuring dynamicBootstrapNodes"
)
info "Connecting to dynamic bootstrap peers"
try:
await connectToNodes(node, dynamicBootstrapNodes, "dynamic bootstrap")
Expand Down
10 changes: 10 additions & 0 deletions waku/waku_core/codecs.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const
WakuRelayCodec* = "/vac/waku/relay/2.0.0"
WakuStoreCodec* = "/vac/waku/store-query/3.0.0"
WakuFilterSubscribeCodec* = "/vac/waku/filter-subscribe/2.0.0-beta1"
WakuFilterPushCodec* = "/vac/waku/filter-push/2.0.0-beta1"
WakuLightPushCodec* = "/vac/waku/lightpush/2.0.0-beta1"
WakuSyncCodec* = "/vac/waku/sync/1.0.0"
WakuMetadataCodec* = "/vac/waku/metadata/1.0.0"
WakuPeerExchangeCodec* = "/vac/waku/peer-exchange/2.0.0-alpha1"
WakuLegacyStoreCodec* = "/vac/waku/store/2.0.0-beta4"
16 changes: 14 additions & 2 deletions waku/waku_core/peers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import
std/[options, sequtils, strutils, uri, net],
results,
chronos,
chronicles,
eth/keys,
eth/p2p/discoveryv5/enr,
eth/net/utils,
Expand All @@ -16,6 +17,7 @@ import
libp2p/peerinfo,
libp2p/routing_record,
json_serialization
import ../waku_enr/capabilities

type
Connectedness* = enum
Expand Down Expand Up @@ -243,7 +245,17 @@ proc toRemotePeerInfo*(enr: enr.Record): Result[RemotePeerInfo, cstring] =
if addrs.len == 0:
return err("enr: no addresses in record")

return ok(RemotePeerInfo.init(peerId, addrs, some(enr)))
let protocolsRes = catch:
enr.getCapabilitiesCodecs()

var protocols: seq[string]
if not protocolsRes.isErr():
protocols = protocolsRes.get()
else:
error "Could not retrieve supported protocols from enr",
peerId = peerId, msg = protocolsRes.error.msg

return ok(RemotePeerInfo.init(peerId, addrs, some(enr), protocols))

converter toRemotePeerInfo*(peerRecord: PeerRecord): RemotePeerInfo =
## Converts peer records to dialable RemotePeerInfo
Expand All @@ -256,7 +268,7 @@ converter toRemotePeerInfo*(peerInfo: PeerInfo): RemotePeerInfo =
RemotePeerInfo(
peerId: peerInfo.peerId,
addrs: peerInfo.listenAddrs,
enr: none(Record),
enr: none(enr.Record),
protocols: peerInfo.protocols,
agent: peerInfo.agentVersion,
protoVersion: peerInfo.protoVersion,
Expand Down
17 changes: 15 additions & 2 deletions waku/waku_enr/capabilities.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{.push raises: [].}

import std/[options, bitops, sequtils, net], results, eth/keys, libp2p/crypto/crypto
import ../common/enr
import
std/[options, bitops, sequtils, net, tables], results, eth/keys, libp2p/crypto/crypto
import ../common/enr, ../waku_core/codecs

const CapabilitiesEnrField* = "waku2"

Expand All @@ -20,6 +21,14 @@ type
Lightpush = 3
Sync = 4

const capabilityToCodec = {
Capabilities.Relay: WakuRelayCodec,
Capabilities.Store: WakuStoreCodec,
Capabilities.Filter: WakuFilterSubscribeCodec,
Capabilities.Lightpush: WakuLightPushCodec,
Capabilities.Sync: WakuSyncCodec,
}.toTable

func init*(
T: type CapabilitiesBitfield, lightpush, filter, store, relay, sync: bool = false
): T =
Expand Down Expand Up @@ -101,3 +110,7 @@ proc getCapabilities*(r: Record): seq[Capabilities] =

let bitfield = bitfieldOpt.get()
bitfield.toCapabilities()

proc getCapabilitiesCodecs*(r: Record): seq[string] {.raises: [ValueError].} =
let capabilities = r.getCapabilities()
return capabilities.mapIt(capabilityToCodec[it])
5 changes: 2 additions & 3 deletions waku/waku_filter_v2/common.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import results

const
WakuFilterSubscribeCodec* = "/vac/waku/filter-subscribe/2.0.0-beta1"
WakuFilterPushCodec* = "/vac/waku/filter-push/2.0.0-beta1"
from ../waku_core/codecs import WakuFilterSubscribeCodec, WakuFilterPushCodec
export WakuFilterSubscribeCodec, WakuFilterPushCodec

type
FilterSubscribeErrorKind* {.pure.} = enum
Expand Down
3 changes: 2 additions & 1 deletion waku/waku_lightpush/common.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import results, chronos, libp2p/peerid
import ../waku_core

const WakuLightPushCodec* = "/vac/waku/lightpush/2.0.0-beta1"
from ../waku_core/codecs import WakuLightPushCodec
export WakuLightPushCodec

type WakuLightPushResult*[T] = Result[T, string]

Expand Down
Loading
Loading