diff --git a/apps/wakunode2/wakunode2.nim b/apps/wakunode2/wakunode2.nim index 3ad4e75637..11d54b6947 100644 --- a/apps/wakunode2/wakunode2.nim +++ b/apps/wakunode2/wakunode2.nim @@ -278,61 +278,77 @@ proc initNode(conf: WakuNodeConf, let pStorage = if peerStore.isNone(): nil 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: - node = WakuNode.new(nodekey, - conf.listenAddress, Port(uint16(conf.tcpPort) + conf.portsShift), - extIp, extPort, - extMultiAddrs, - pStorage, - conf.maxConnections.int, - Port(uint16(conf.websocketPort) + conf.portsShift), - conf.websocketSupport, - conf.websocketSecureSupport, - conf.websocketSecureKeyPath, - conf.websocketSecureCertPath, - some(wakuFlags), - dnsResolver, - conf.relayPeerExchange, # We send our own signed peer record when peer exchange enabled - dns4DomainName, - discv5UdpPort, - some(conf.agentString), - conf.peerStoreCapacity, - rng) + netConfigOpt = some(NetConfig.init( + bindIp = conf.listenAddress, + bindPort = Port(uint16(conf.tcpPort) + conf.portsShift), + extIp = extIp, + extPort = extPort, + extMultiAddrs = extMultiAddrs, + wsBindPort = Port(uint16(conf.websocketPort) + conf.portsShift), + wsEnabled = conf.websocketSupport, + wssEnabled = conf.websocketSecureSupport, + dns4DomainName = dns4DomainName, + discv5UdpPort = discv5UdpPort, + wakuFlags = some(wakuFlags), + )) except: - return err("failed to create waku node instance: " & getCurrentExceptionMsg()) + return err("failed to create net config instance: " & getCurrentExceptionMsg()) - if conf.discv5Discovery: - let - discoveryConfig = DiscoveryConfig.init( - conf.discv5TableIpLimit, conf.discv5BucketIpLimit, conf.discv5BitsPerHop) + let netConfig = netConfigOpt.get() + var wakuDiscv5 = none(WakuDiscoveryV5) - # 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) + if conf.discv5Discovery: + let dynamicBootstrapEnrs = dynamicBootstrapNodes + .filterIt(it.hasUdpPort()) + .mapIt(it.enr.get()) 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 for enrUri in conf.discv5BootstrapNodes: addBootstrapNode(enrUri, discv5BootstrapEnrs) - - node.wakuDiscv5 = WakuDiscoveryV5.new( - extIP, extPort, discv5UdpPort, - conf.listenAddress, - discv5UdpPort.get(), - discv5BootstrapEnrs, - conf.discv5EnrAutoUpdate, - keys.PrivateKey(nodekey.skkey), - wakuFlags, - [], # Empty enr fields, for now - node.rng, - discoveryConfig - ) + discv5BootstrapEnrs.add(dynamicBootstrapEnrs) + let discv5Config = DiscoveryConfig.init(conf.discv5TableIpLimit, + conf.discv5BucketIpLimit, + conf.discv5BitsPerHop) + try: + wakuDiscv5 = some(WakuDiscoveryV5.new( + extIp = netConfig.extIp, + extTcpPort = netConfig.extPort, + extUdpPort = netConfig.discv5UdpPort, + bindIp = netConfig.bindIp, + discv5UdpPort = netConfig.discv5UdpPort.get(), + 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) diff --git a/examples/v2/publisher.nim b/examples/v2/publisher.nim index 1779335890..ed7557c4b0 100644 --- a/examples/v2/publisher.nim +++ b/examples/v2/publisher.nim @@ -48,7 +48,6 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} = bootstrapNodes = bootstrapNodes, privateKey = keys.PrivateKey(nodeKey.skkey), flags = flags, - enrFields = [], rng = node.rng) await node.start() diff --git a/examples/v2/subscriber.nim b/examples/v2/subscriber.nim index 71b0259b7d..2f9f2a59ed 100644 --- a/examples/v2/subscriber.nim +++ b/examples/v2/subscriber.nim @@ -44,7 +44,6 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} = bootstrapNodes = bootstrapNodes, privateKey = keys.PrivateKey(nodeKey.skkey), flags = flags, - enrFields = [], rng = node.rng) await node.start() diff --git a/tests/v2/test_enr_utils.nim b/tests/v2/test_enr_utils.nim index 618fe0abae..fb9ae1a8cc 100644 --- a/tests/v2/test_enr_utils.nim +++ b/tests/v2/test_enr_utils.nim @@ -22,10 +22,10 @@ procSuite "ENR utils": tooShort = "0x000A047F0000010601BADD0301".hexToSeqByte() gibberish = "0x3270ac4e5011123c".hexToSeqByte() empty = newSeq[byte]() - + ## Note: we expect to fail optimistically, i.e. extract ## 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: # Expected cases 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")[]] let - record = initEnr(enrKey, some(enrIp), + record = enr.Record.init(enrKey, some(enrIp), some(enrTcpPort), some(enrUdpPort), some(wakuFlags), multiaddrs) typedRecord = record.toTypedRecord.get() - + # Check EIP-778 ENR fields check: @(typedRecord.secp256k1.get()) == enrKey.getPublicKey()[].getRawBytes()[] ipv4(typedRecord.ip.get()) == enrIp Port(typedRecord.tcp.get()) == enrTcpPort Port(typedRecord.udp.get()) == enrUdpPort - + # Check Waku ENR fields let decodedFlags = record.get(WAKU_ENR_FIELD, seq[byte])[] @@ -71,7 +71,7 @@ procSuite "ENR utils": 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/443/wss")[]) - + asyncTest "Strip multiaddr peerId": # Tests that peerId is stripped of multiaddrs as per RFC31 let @@ -81,7 +81,7 @@ procSuite "ENR utils": multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD")[]] let - record = initEnr(enrKey, some(enrIp), + record = enr.Record.init(enrKey, some(enrIp), some(enrTcpPort), some(enrUdpPort), none(WakuEnrBitfield), multiaddrs) @@ -89,7 +89,7 @@ procSuite "ENR utils": # Check Waku ENR fields let 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 asyncTest "Decode ENR with multiaddrs field": @@ -110,7 +110,7 @@ procSuite "ENR utils": var enrRecord: Record check: enrRecord.fromURI(knownEnr) - + let typedRecord = enrRecord.toTypedRecord.get() # Check EIP-778 ENR fields @@ -118,11 +118,11 @@ procSuite "ENR utils": ipv4(typedRecord.ip.get()) == knownIp typedRecord.tcp == knownTcpPort typedRecord.udp == knownUdpPort - + # Check Waku ENR fields let decodedAddrs = enrRecord.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses() - + for knownMultiaddr in knownMultiaddrs: check decodedAddrs.contains(knownMultiaddr) @@ -132,12 +132,12 @@ procSuite "ENR utils": enrTcpPort, enrUdpPort = Port(60000) enrKey = wakuenr.crypto.PrivateKey.random(Secp256k1, rng[])[] 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 # test all flag combinations 2^4 = 16 (b0000-b1111) records = toSeq(0b0000_0000'u8..0b0000_1111'u8) - .mapIt(initEnr(enrKey, + .mapIt(enr.Record.init(enrKey, some(enrIp), some(enrTcpPort), some(enrUdpPort), @@ -161,7 +161,7 @@ procSuite "ENR utils": [true, true, false, true], [true, true, true, false], [true, true, true, true]] - + for i, record in records: for j, capability in @[Lightpush, Filter, Store, Relay]: check expectedCapabilities[i][j] == record.supportsCapability(capability) @@ -172,18 +172,18 @@ procSuite "ENR utils": enrTcpPort, enrUdpPort = Port(60000) enrKey = wakuenr.crypto.PrivateKey.random(Secp256k1, rng[])[] multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]] - + records = @[0b0000_0000'u8, 0b0000_1111'u8, 0b0000_1001'u8, 0b0000_1110'u8, 0b0000_1000'u8,] - .mapIt(initEnr(enrKey, - some(enrIp), - some(enrTcpPort), - some(enrUdpPort), - some(uint8(it)), - multiaddrs)) + .mapIt(enr.Record.init(enrKey, + some(enrIp), + some(enrTcpPort), + some(enrUdpPort), + some(uint8(it)), + multiaddrs)) # expected capabilities, ordered LSB to MSB expectedCapabilities: seq[seq[Capabilities]] = @[ @@ -197,14 +197,14 @@ procSuite "ENR utils": check actualExpetedTuple[0].getCapabilities() == actualExpetedTuple[1] 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"& "xb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNl"& "Y3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA" - + var nonWakuEnrRecord: Record - + check: nonWakuEnrRecord.fromURI(nonWakuEnr) @@ -214,4 +214,4 @@ procSuite "ENR utils": nonWakuEnrRecord.supportsCapability(Relay) == false nonWakuEnrRecord.supportsCapability(Store) == false nonWakuEnrRecord.supportsCapability(Filter) == false - nonWakuEnrRecord.supportsCapability(Lightpush) == false \ No newline at end of file + nonWakuEnrRecord.supportsCapability(Lightpush) == false diff --git a/tests/v2/test_waku_discv5.nim b/tests/v2/test_waku_discv5.nim index 2579f090d7..c088bd96bf 100644 --- a/tests/v2/test_waku_discv5.nim +++ b/tests/v2/test_waku_discv5.nim @@ -2,6 +2,7 @@ import chronos, + chronicles, testutils/unittests, stew/byteutils, stew/shims/net, @@ -25,7 +26,7 @@ procSuite "Waku Discovery v5": nodeTcpPort1 = Port(61500) nodeUdpPort1 = Port(9000) node1 = WakuNode.new(nodeKey1, bindIp, nodeTcpPort1) - + nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[] nodeTcpPort2 = Port(61502) nodeUdpPort2 = Port(9002) @@ -46,7 +47,7 @@ procSuite "Waku Discovery v5": 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), @@ -56,10 +57,10 @@ procSuite "Waku Discovery v5": false, keys.PrivateKey(nodeKey1.skkey), flags, - [], # Empty enr fields, for now + newSeq[MultiAddress](), # Empty multiaddr fields, for now node1.rng ) - + node2.wakuDiscv5 = WakuDiscoveryV5.new( some(extIp), some(nodeTcpPort2), some(nodeUdpPort2), bindIp, @@ -68,10 +69,10 @@ procSuite "Waku Discovery v5": false, keys.PrivateKey(nodeKey2.skkey), flags, - [], # Empty enr fields, for now + newSeq[MultiAddress](), # Empty multiaddr fields, for now node2.rng ) - + node3.wakuDiscv5 = WakuDiscoveryV5.new( some(extIp), some(nodeTcpPort3), some(nodeUdpPort3), bindIp, @@ -80,7 +81,7 @@ procSuite "Waku Discovery v5": false, keys.PrivateKey(nodeKey3.skkey), flags, - [], # Empty enr fields, for now + newSeq[MultiAddress](), # Empty multiaddr fields, for now node3.rng ) @@ -97,7 +98,7 @@ procSuite "Waku Discovery v5": 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.} = @@ -119,3 +120,76 @@ procSuite "Waku Discovery v5": # (await completionFut.withTimeout(6.seconds)) == true 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) + diff --git a/tests/v2/test_waku_peer_exchange.nim b/tests/v2/test_waku_peer_exchange.nim index eb851f79b7..c331fb159c 100644 --- a/tests/v2/test_waku_peer_exchange.nim +++ b/tests/v2/test_waku_peer_exchange.nim @@ -103,7 +103,7 @@ procSuite "Waku Peer Exchange": false, keys.PrivateKey(nodeKey1.skkey), flags, - [], # Empty enr fields, for now + newSeq[MultiAddress](), # Empty multiaddr fields, for now node1.rng ) @@ -115,7 +115,7 @@ procSuite "Waku Peer Exchange": false, keys.PrivateKey(nodeKey2.skkey), flags, - [], # Empty enr fields, for now + newSeq[MultiAddress](), # Empty multiaddr fields, for now node2.rng ) diff --git a/waku/v2/node/discv5/waku_discv5.nim b/waku/v2/node/discv5/waku_discv5.nim index 690e2ae8b1..bbafccd887 100644 --- a/waku/v2/node/discv5/waku_discv5.nim +++ b/waku/v2/node/discv5/waku_discv5.nim @@ -112,18 +112,20 @@ proc new*(T: type WakuDiscoveryV5, extTcpPort, extUdpPort: Option[Port], bindIP: ValidIpAddress, discv5UdpPort: Port, - bootstrapEnrs: seq[enr.Record], + bootstrapEnrs = newSeq[enr.Record](), enrAutoUpdate = false, privateKey: keys.PrivateKey, flags: WakuEnrBitfield, - enrFields: openArray[(string, seq[byte])], + multiaddrs = newSeq[MultiAddress](), rng: ref HmacDrbgContext, discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T = ## TODO: consider loading from a configurable bootstrap file ## We always add the waku field as specified var enrInitFields = @[(WAKU_ENR_FIELD, @[flags.byte])] - enrInitFields.add(enrFields) + + ## Add multiaddresses to ENR + enrInitFields.add((MULTIADDR_ENR_FIELD, multiaddrs.getRawField())) let protocol = newProtocol( privateKey, @@ -148,7 +150,7 @@ proc new*(T: type WakuDiscoveryV5, enrAutoUpdate = false, privateKey: keys.PrivateKey, flags: WakuEnrBitfield, - enrFields: openArray[(string, seq[byte])], + multiaddrs = newSeq[MultiAddress](), rng: ref HmacDrbgContext, discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T = @@ -164,7 +166,7 @@ proc new*(T: type WakuDiscoveryV5, enrAutoUpdate, privateKey, flags, - enrFields, + multiaddrs, rng, discv5Config ) diff --git a/waku/v2/node/waku_node.nim b/waku/v2/node/waku_node.nim index 48e16c937f..d18357064e 100644 --- a/waku/v2/node/waku_node.nim +++ b/waku/v2/node/waku_node.nim @@ -131,38 +131,47 @@ template wsFlag(wssEnabled: bool): MultiAddress = if wssEnabled: MultiAddress.init("/wss").tryGet() else: MultiAddress.init("/ws").tryGet() -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), - 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. - +type NetConfig* = object + hostAddress*: MultiAddress + wsHostAddress*: Option[MultiAddress] + hostExtAddress*: Option[MultiAddress] + wsExtAddress*: Option[MultiAddress] + wssEnabled*: bool + extIp*: Option[ValidIpAddress] + extPort*: Option[Port] + dns4DomainName*: Option[string] + announcedAddresses*: seq[MultiAddress] + extMultiAddrs*: seq[MultiAddress] + enrMultiAddrs*: seq[MultiAddress] + enrIp*: Option[ValidIpAddress] + enrPort*: Option[Port] + discv5UdpPort*: Option[Port] + wakuFlags*: Option[WakuEnrBitfield] + bindIp*: ValidIpAddress + bindPort*: Port + +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 let # Bind addresses hostAddress = ip4TcpEndPoint(bindIp, bindPort) wsHostAddress = if wsEnabled or wssEnabled: some(ip4TcpEndPoint(bindIp, wsbindPort) & wsFlag(wssEnabled)) else: none(MultiAddress) + enrIp = if extIp.isSome(): extIp else: some(bindIp) + enrPort = if extPort.isSome(): extPort else: some(bindPort) # Setup external addresses, if available var @@ -198,25 +207,48 @@ proc new*(T: type WakuNode, elif wsHostAddress.isSome(): announcedAddresses.add(wsHostAddress.get()) - ## Initialize peer 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 # 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, - 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 ## flagging us as Reachable or NotReachable. ## minConfidence is used as threshold to determine the state. @@ -237,36 +269,107 @@ proc new*(T: type WakuNode, 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( some(nodekey), - hostAddress, - wsHostAddress, + address = netConfig.hostAddress, + wsAddress = netConfig.wsHostAddress, transportFlags = {ServerFlags.ReuseAddr}, rng = rng, maxConnections = maxConnections, - wssEnabled = wssEnabled, + wssEnabled = netConfig.wssEnabled, secureKeyPath = secureKey, secureCertPath = secureCert, nameResolver = nameResolver, sendSignedPeerRecord = sendSignedPeerRecord, agentString = agentString, peerStoreCapacity = peerStoreCapacity, - services = @[Service(autonatservice)], + services = @[Service(getAutonatService(rng))], ) - let wakuNode = WakuNode( + return WakuNode( peerManager: PeerManager.new(switch, peerStorage), switch: switch, rng: rng, - enr: enr, - announcedAddresses: announcedAddresses + enr: netConfig.getEnr(wakuDiscv5, nodekey), + announcedAddresses: netConfig.announcedAddresses, + wakuDiscv5: if wakuDiscV5.isSome(): wakuDiscV5.get() else: nil, ) - return wakuNode - - proc peerInfo*(node: WakuNode): PeerInfo = node.switch.peerInfo diff --git a/waku/v2/utils/peers.nim b/waku/v2/utils/peers.nim index 38eaefdd28..0c233a6e0b 100644 --- a/waku/v2/utils/peers.nim +++ b/waku/v2/utils/peers.nim @@ -40,7 +40,7 @@ proc init*( addrs: addrs, enr: enr, protocols: protocols) - + return remotePeerInfo proc init*(p: typedesc[RemotePeerInfo], @@ -49,7 +49,7 @@ proc init*(p: typedesc[RemotePeerInfo], enr: Option[enr.Record] = none(enr.Record), protocols: seq[string] = @[]): RemotePeerInfo {.raises: [Defect, ResultError[cstring], LPError].} = - + let remotePeerInfo = RemotePeerInfo( peerId: PeerID.init(peerId).tryGet(), 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 let - peerIdStr = p2pPart.toString()[].split("/")[^1] + peerIdStr = p2pPart.toString()[].split("/")[^1] wireAddr = nwPart & tcpPart & wsPart & wssPart if (not wireAddr.validWireAddr()): @@ -111,12 +111,12 @@ proc toRemotePeerInfo*(enr: enr.Record): Result[RemotePeerInfo, cstring] = if not typedR.secp256k1.isSome: return err("enr: no secp256k1 key in record") - + let pubKey = ? keys.PublicKey.fromRaw(typedR.secp256k1.get) peerId = ? PeerID.init(crypto.PublicKey(scheme: Secp256k1, skkey: secp.SkPublicKey(pubKey))) - + var addrs = newSeq[MultiAddress]() let transportProto = getTransportProtocol(typedR) @@ -182,3 +182,18 @@ proc hasProtocol*(ma: MultiAddress, proto: string): bool = if p == MultiCodec.codec(proto): return true 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() + diff --git a/waku/v2/utils/wakuenr.nim b/waku/v2/utils/wakuenr.nim index 403043caf4..8154377af7 100644 --- a/waku/v2/utils/wakuenr.nim +++ b/waku/v2/utils/wakuenr.nim @@ -34,21 +34,25 @@ type Filter = 2, Lightpush = 3, -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/ - +func getRawField*(multiaddrs: seq[MultiAddress]): seq[byte] = var fieldRaw: seq[byte] for multiaddr in multiaddrs: let maRaw = multiaddr.data.buffer # binary encoded multiaddr maSize = maRaw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer - + assert maSize.len == 2 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) func stripPeerId(multiaddr: MultiAddress): MultiAddress = @@ -58,10 +62,10 @@ func stripPeerId(multiaddr: MultiAddress): MultiAddress = if item[].protoName()[] != "p2p": # Add all parts except p2p peerId discard cleanAddr.append(item[]) - + return cleanAddr -func stripPeerIds(multiaddrs: seq[MultiAddress]): seq[MultiAddress] = +func stripPeerIds*(multiaddrs: seq[MultiAddress]): seq[MultiAddress] = var cleanAddrs: seq[MultiAddress] for multiaddr in multiaddrs: @@ -69,19 +73,19 @@ func stripPeerIds(multiaddrs: seq[MultiAddress]): seq[MultiAddress] = cleanAddrs.add(multiaddr.stripPeerId()) else: cleanAddrs.add(multiaddr) - + return cleanAddrs 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 ## an error if `rawBytes` boundary is exceeded. - ## + ## ## If successful, `pos` is advanced by `numBytes` if rawBytes[pos..^1].len() < numBytes: return err("Exceeds maximum available bytes") - + let slicedSeq = rawBytes[pos..