From d09ec815ceda7ff3e5b4aa3c6a8fe3f7fbd09a4e Mon Sep 17 00:00:00 2001 From: Aaryamann Challani <43716372+rymnc@users.noreply.github.com> Date: Thu, 26 Jan 2023 15:48:30 +0530 Subject: [PATCH] feat(wakunode): advertise custom multiaddresses (#1509) * feat(wakunode2): ability to advertise custom multiaddresses * test(wakunode): test the feature * fix(wakunode): remove rln diff * revert(rln-relay): change that creeped into the diff * fix(wakunode): move extMultiaddrs closer to nat * fix(waku_node): idiomatic default arg * fix(config): shortened validation * fix(wakunode): discoverable via discv5 and dnsdisc --- apps/wakunode2/config.nim | 11 ++++++++ apps/wakunode2/wakunode2.nim | 12 +++++++-- tests/v2/test_wakunode.nim | 49 ++++++++++++++++++++++++++++++------ waku/v2/node/waku_node.nim | 18 ++++++++++--- waku/v2/utils/peers.nim | 13 ++++++++++ 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/apps/wakunode2/config.nim b/apps/wakunode2/config.nim index fc068eef6..d12bd572e 100644 --- a/apps/wakunode2/config.nim +++ b/apps/wakunode2/config.nim @@ -10,6 +10,7 @@ import confutils/toml/std/net as confTomlNet, libp2p/crypto/crypto, libp2p/crypto/secp, + libp2p/multiaddress, nimcrypto/utils import ../../waku/common/confutils/envvar/defs as confEnvvarDefs, @@ -75,6 +76,10 @@ type "Must be one of: any, none, upnp, pmp, extip:." defaultValue: "any" }: string + extMultiAddrs* {. + desc: "External multiaddresses to advertise to the network. Argument may be repeated." + name: "ext-multiaddr" }: seq[string] + maxConnections* {. desc: "Maximum allowed number of libp2p connections." defaultValue: 50 @@ -513,6 +518,12 @@ proc validateStoreMessageRetentionPolicy*(val: string): ConfResult[string] = else: err("invalid 'store message retention policy' option format: " & val) +proc validateExtMultiAddrs*(vals: seq[string]): ConfResult[seq[MultiAddress]] = + var multiaddrs: seq[MultiAddress] + for val in vals: + let multiaddr = ? MultiAddress.init(val) + multiaddrs.add(multiaddr) + ok(multiaddrs) ## Load diff --git a/apps/wakunode2/wakunode2.nim b/apps/wakunode2/wakunode2.nim index 4b58f5905..793ea9981 100644 --- a/apps/wakunode2/wakunode2.nim +++ b/apps/wakunode2/wakunode2.nim @@ -252,6 +252,14 @@ proc initNode(conf: WakuNodeConf, some(Port(uint16(conf.tcpPort) + conf.portsShift)) else: extTcpPort + extMultiAddrs = if (conf.extMultiAddrs.len > 0): + let extMultiAddrsValidationRes = validateExtMultiAddrs(conf.extMultiAddrs) + if extMultiAddrsValidationRes.isErr(): + return err("invalid external multiaddress: " & extMultiAddrsValidationRes.error) + else: + extMultiAddrsValidationRes.get() + else: + @[] wakuFlags = initWakuFlags(conf.lightpush, conf.filter, @@ -266,6 +274,7 @@ proc initNode(conf: WakuNodeConf, node = WakuNode.new(conf.nodekey, conf.listenAddress, Port(uint16(conf.tcpPort) + conf.portsShift), extIp, extPort, + extMultiAddrs, pStorage, conf.maxConnections.int, Port(uint16(conf.websocketPort) + conf.portsShift), @@ -279,8 +288,7 @@ proc initNode(conf: WakuNodeConf, dns4DomainName, discv5UdpPort, some(conf.agentString), - some(conf.peerStoreCapacity), - ) + some(conf.peerStoreCapacity)) except: return err("failed to create waku node instance: " & getCurrentExceptionMsg()) diff --git a/tests/v2/test_wakunode.nim b/tests/v2/test_wakunode.nim index 15d8466a6..4e8a207a3 100644 --- a/tests/v2/test_wakunode.nim +++ b/tests/v2/test_wakunode.nim @@ -4,8 +4,8 @@ import stew/byteutils, stew/shims/net as stewNet, testutils/unittests, - chronicles, - chronos, + chronicles, + chronos, libp2p/crypto/crypto, libp2p/crypto/secp, libp2p/multiaddress, @@ -24,7 +24,7 @@ import procSuite "WakuNode": let rng = crypto.newRng() - + asyncTest "Protocol matcher works as expected": let nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[] @@ -153,9 +153,9 @@ procSuite "WakuNode": expect IOError: # gibberish discard WakuNode.new(nodeKey1, ValidIpAddress.init("0.0.0.0"), - bindPort = Port(61004), - wsBindPort = Port(8000), - wssEnabled = true, + bindPort = Port(61004), + wsBindPort = Port(8000), + wssEnabled = true, secureKey = "../../waku/v2/node/key_dummy.txt") asyncTest "Peer info updates with correct announced addresses": @@ -216,7 +216,7 @@ procSuite "WakuNode": node.announcedAddresses.len == 1 node.announcedAddresses.contains(expectedDns4Addr) - + asyncTest "Agent string is set and advertised correctly": let # custom agent string @@ -250,4 +250,37 @@ procSuite "WakuNode": node1Agent == expectedAgentString1 node2Agent == expectedAgentString2 - await allFutures(node1.stop(), node2.stop()) \ No newline at end of file + await allFutures(node1.stop(), node2.stop()) + + asyncTest "Custom multiaddresses are set and advertised correctly": + let + # custom multiaddress + expectedMultiaddress1 = MultiAddress.init("/ip4/200.200.200.200/tcp/1234").get() + + # Note: this could have been done with a single node, but it is useful to + # have two nodes to check that the multiaddress is advertised correctly + let + # node with custom multiaddress + nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[] + node1 = WakuNode.new(nodeKey1, ValidIpAddress.init("0.0.0.0"), Port(61018), + extMultiAddrs = @[expectedMultiaddress1]) + + # node with default multiaddress + nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[] + node2 = WakuNode.new(nodeKey2, ValidIpAddress.init("0.0.0.0"), Port(61020)) + + await node1.start() + await node1.mountRelay() + + await node2.start() + await node2.mountRelay() + + await node1.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) + await node2.connectToNodes(@[node1.switch.peerInfo.toRemotePeerInfo()]) + + let node1MultiAddrs = node2.switch.peerStore[AddressBook][node1.switch.peerInfo.toRemotePeerInfo().peerId] + + check: + node1MultiAddrs.contains(expectedMultiaddress1) + + await allFutures(node1.stop(), node2.stop()) diff --git a/waku/v2/node/waku_node.nim b/waku/v2/node/waku_node.nim index 96f16b650..841d87854 100644 --- a/waku/v2/node/waku_node.nim +++ b/waku/v2/node/waku_node.nim @@ -136,6 +136,7 @@ proc new*(T: type WakuNode, bindPort: Port, extIp = none(ValidIpAddress), extPort = none(Port), + extMultiAddrs = newSeq[MultiAddress](), peerStorage: PeerStorage = nil, maxConnections = builders.MaxConnections, wsBindPort: Port = (Port)8000, @@ -178,12 +179,17 @@ proc new*(T: type WakuNode, if (wsHostAddress.isSome()): wsExtAddress = some(ip4TcpEndPoint(extIp.get(), wsBindPort) & wsFlag(wssEnabled)) - var announcedAddresses: seq[MultiAddress] + var announcedAddresses = newSeq[MultiAddress]() + if hostExtAddress.isSome(): announcedAddresses.add(hostExtAddress.get()) else: announcedAddresses.add(hostAddress) # We always have at least a bind address for the host + # External multiaddrs that the operator may have configured + if extMultiAddrs.len > 0: + announcedAddresses.add(extMultiAddrs) + if wsExtAddress.isSome(): announcedAddresses.add(wsExtAddress.get()) elif wsHostAddress.isSome(): @@ -196,9 +202,12 @@ proc new*(T: type WakuNode, else: some(bindIp) enrTcpPort = if extPort.isSome(): extPort else: some(bindPort) - enrMultiaddrs = if wsExtAddress.isSome(): @[wsExtAddress.get()] # Only add ws/wss to `multiaddrs` field - elif wsHostAddress.isSome(): @[wsHostAddress.get()] - else: @[] + # enrMultiaddrs are just addresses which cannot be represented in ENR, as described in + # https://rfc.vac.dev/spec/31/#many-connection-types + enrMultiaddrs = announcedAddresses.filterIt(it.hasProtocol("dns4") or + it.hasProtocol("dns6") or + it.hasProtocol("ws") or + it.hasProtocol("wss")) enr = initEnr(nodeKey, enrIp, enrTcpPort, @@ -816,6 +825,7 @@ when defined(rln): return node.wakuRlnRelay = rlnRelayRes.get() + ## Waku peer-exchange proc mountPeerExchange*(node: WakuNode) {.async, raises: [Defect, LPError].} = diff --git a/waku/v2/utils/peers.nim b/waku/v2/utils/peers.nim index 4c74a08e4..38eaefdd2 100644 --- a/waku/v2/utils/peers.nim +++ b/waku/v2/utils/peers.nim @@ -13,6 +13,7 @@ import libp2p/crypto/[crypto, secp], libp2p/[errors, multiaddress, + multicodec, peerid, peerinfo, routing_record] @@ -169,3 +170,15 @@ proc toRemotePeerInfo*(peerInfo: PeerInfo): RemotePeerInfo = peerInfo.listenAddrs, none(enr.Record), # we could generate an ENR from PeerInfo peerInfo.protocols) + +## Checks if a multiaddress contains a given protocol +## Useful for filtering multiaddresses based on their protocols +proc hasProtocol*(ma: MultiAddress, proto: string): bool = + ## Returns ``true`` if ``ma`` contains protocol ``proto``. + let protos = ma.protocols() + if protos.isErr(): + return false + for p in protos.get(): + if p == MultiCodec.codec(proto): + return true + return false