From 2ffd2f80107cd5a64180b100200b656d496cc0a4 Mon Sep 17 00:00:00 2001 From: Lorenzo Delgado Date: Thu, 30 Mar 2023 09:35:13 +0200 Subject: [PATCH] refactor(enr): move waku enr multiaddr to typedrecod and builder extensions --- examples/v2/publisher.nim | 16 +- examples/v2/subscriber.nim | 12 +- tests/v2/test_waku_discv5.nim | 8 +- tests/v2/test_waku_enr.nim | 224 +++++++++++++++------------ tests/v2/test_waku_peer_exchange.nim | 4 +- waku/v2/protocol/waku_discv5.nim | 143 +++++++---------- waku/v2/protocol/waku_enr.nim | 118 +++++++------- 7 files changed, 269 insertions(+), 256 deletions(-) diff --git a/examples/v2/publisher.nim b/examples/v2/publisher.nim index 11880ad83..b5c403e8b 100644 --- a/examples/v2/publisher.nim +++ b/examples/v2/publisher.nim @@ -22,7 +22,14 @@ proc now*(): Timestamp = getNanosecondTime(getTime().toUnixFloat()) # An accesible bootstrap node. See wakuv2.prod fleets.status.im -const bootstrapNodes = @["enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP"] + + +const bootstrapNode = "enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9D" & + "OGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgn" & + "Y0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY" & + "24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQG" & + "H0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvY" & + "j5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP" # careful if running pub and sub in the same machine const wakuPort = 60000 @@ -33,11 +40,14 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} = setupLogLevel(logging.LogLevel.NOTICE) notice "starting publisher", wakuPort=wakuPort, discv5Port=discv5Port let - nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[] + nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).get() ip = ValidIpAddress.init("0.0.0.0") node = WakuNode.new(nodeKey, ip, Port(wakuPort)) flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true) + var bootstrapNodeEnr: enr.Record + discard bootstrapNodeEnr.fromURI(bootstrapNode) + # assumes behind a firewall, so not care about being discoverable node.wakuDiscv5 = WakuDiscoveryV5.new( extIp= none(ValidIpAddress), @@ -45,7 +55,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} = extUdpPort = none(Port), bindIP = ip, discv5UdpPort = Port(discv5Port), - bootstrapNodes = bootstrapNodes, + bootstrapEnrs = @[bootstrapNodeEnr], privateKey = keys.PrivateKey(nodeKey.skkey), flags = flags, rng = node.rng) diff --git a/examples/v2/subscriber.nim b/examples/v2/subscriber.nim index 654fa36f9..49c70e0e5 100644 --- a/examples/v2/subscriber.nim +++ b/examples/v2/subscriber.nim @@ -18,7 +18,12 @@ import ../../../waku/v2/protocol/waku_discv5 # An accesible bootstrap node. See wakuv2.prod fleets.status.im -const bootstrapNodes = @["enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP"] +const bootstrapNode = "enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZl" & + "K0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS" & + "3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmct" & + "Yy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGh" & + "Ao0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1" & + "ZHCCIyiFd2FrdTIP" # careful if running pub and sub in the same machine const wakuPort = 50000 @@ -34,6 +39,9 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} = node = WakuNode.new(nodeKey, ip, Port(wakuPort)) flags = CapabilitiesBitfield.init(lightpush = false, filter = false, store = false, relay = true) + var bootstrapNodeEnr: enr.Record + discard bootstrapNodeEnr.fromURI(bootstrapNode) + # assumes behind a firewall, so not care about being discoverable node.wakuDiscv5 = WakuDiscoveryV5.new( extIp= none(ValidIpAddress), @@ -41,7 +49,7 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} = extUdpPort = none(Port), bindIP = ip, discv5UdpPort = Port(discv5Port), - bootstrapNodes = bootstrapNodes, + bootstrapEnrs = @[bootstrapNodeEnr], privateKey = keys.PrivateKey(nodeKey.skkey), flags = flags, rng = node.rng) diff --git a/tests/v2/test_waku_discv5.nim b/tests/v2/test_waku_discv5.nim index dbc73e22d..208cb60d0 100644 --- a/tests/v2/test_waku_discv5.nim +++ b/tests/v2/test_waku_discv5.nim @@ -56,7 +56,7 @@ procSuite "Waku Discovery v5": some(extIp), some(nodeTcpPort1), some(nodeUdpPort1), bindIp, nodeUdpPort1, - newSeq[string](), + newSeq[enr.Record](), false, keys.PrivateKey(nodeKey1.skkey), flags, @@ -68,7 +68,7 @@ procSuite "Waku Discovery v5": some(extIp), some(nodeTcpPort2), some(nodeUdpPort2), bindIp, nodeUdpPort2, - @[node1.wakuDiscv5.protocol.localNode.record.toURI()], # Bootstrap with node1 + @[node1.wakuDiscv5.protocol.localNode.record], # Bootstrap with node1 false, keys.PrivateKey(nodeKey2.skkey), flags, @@ -80,7 +80,7 @@ procSuite "Waku Discovery v5": some(extIp), some(nodeTcpPort3), some(nodeUdpPort3), bindIp, nodeUdpPort3, - @[node2.wakuDiscv5.protocol.localNode.record.toURI()], # Bootstrap with node2 + @[node2.wakuDiscv5.protocol.localNode.record], # Bootstrap with node2 false, keys.PrivateKey(nodeKey3.skkey), flags, @@ -191,7 +191,7 @@ procSuite "Waku Discovery v5": 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() + let multiaddrs = node1Enr.toTyped().get().multiaddrs.get() check: node1.wakuDiscv5.protocol.nodesDiscovered > 0 diff --git a/tests/v2/test_waku_enr.nim b/tests/v2/test_waku_enr.nim index 57632cf33..c7988eb46 100644 --- a/tests/v2/test_waku_enr.nim +++ b/tests/v2/test_waku_enr.nim @@ -2,7 +2,7 @@ import std/options, - stew/[results, byteutils], + stew/results, testutils/unittests import ../../waku/v2/protocol/waku_enr, @@ -37,7 +37,7 @@ suite "Waku ENR - Capabilities bitfield": check: caps == @[Capabilities.Relay, Capabilities.Filter, Capabilities.Lightpush] - test "encode and extract capabilities from record (EnrBuilder ext)": + test "encode and decode record with capabilities field (EnrBuilder ext)": ## Given let enrSeqNum = 1u64 @@ -63,7 +63,7 @@ suite "Waku ENR - Capabilities bitfield": check: bitfield.toCapabilities() == @[Capabilities.Relay, Capabilities.Store] - test "encode and extract capabilities from record (deprecated)": + test "encode and decode record with capabilities field (deprecated)": # TODO: Remove after removing the `Record.init()` proc ## Given let enrkey = generatesecp256k1key() @@ -84,7 +84,7 @@ suite "Waku ENR - Capabilities bitfield": check: bitfield.toCapabilities() == @[Capabilities.Relay, Capabilities.Store] - test "cannot extract capabilities from record": + test "cannot decode capabilities from record": ## Given let enrSeqNum = 1u64 @@ -154,115 +154,147 @@ suite "Waku ENR - Capabilities bitfield": suite "Waku ENR - Multiaddresses": - test "Parse multiaddr field": - let - reasonable = "0x000A047F0000010601BADD03".hexToSeqByte() - reasonableDns4 = ("0x002F36286E6F64652D30312E646F2D616D73332E77616B7576322E746" & - "573742E737461747573696D2E6E65740601BBDE03003837316E6F64652D" & - "30312E61632D636E2D686F6E676B6F6E672D632E77616B7576322E74657" & - "3742E737461747573696D2E6E65740601BBDE030029BD03ADADEC040BE0" & - "47F9658668B11A504F3155001F231A37F54C4476C07FB4CC139ED7E30304D2DE03").hexToSeqByte() - tooLong = "0x000B047F0000010601BADD03".hexToSeqByte() - tooShort = "0x000A047F0000010601BADD0301".hexToSeqByte() - gibberish = "0x3270ac4e5011123c".hexToSeqByte() - empty = newSeq[byte]() + test "decode record with multiaddrs field": + ## Given + let enrUri = "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSH" & + "KCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcn" & + "O4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG" & + "73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1" & + "c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1" & + "-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKa" & + "v-g3VkcIIjKA" - ## 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. + var record: Record + require record.fromURI(enrUri) + + let + expectedAddr1 = MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss").get() + expectedAddr2 = MultiAddress.init("/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss").get() + expectedAddr3 = MultiAddress.init("/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss").get() + + ## When + let typedRecord = record.toTyped() + require typedRecord.isOk() + + let multiaddrsOpt = typedRecord.value.multiaddrs + + ## Then + check multiaddrsOpt.isSome() + + let multiaddrs = multiaddrsOpt.get() check: - # Expected cases - reasonable.toMultiAddresses().contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]) - reasonableDns4.toMultiAddresses().contains(MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss")[]) - # Buffer exceeded - tooLong.toMultiAddresses().len() == 0 - # Buffer remainder - tooShort.toMultiAddresses().contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]) - # Gibberish - gibberish.toMultiAddresses().len() == 0 - # Empty - empty.toMultiAddresses().len() == 0 + multiaddrs.len == 3 + multiaddrs.contains(expectedAddr1) + multiaddrs.contains(expectedAddr2) + multiaddrs.contains(expectedAddr3) - test "Init ENR for Waku Usage": - # Tests RFC31 encoding "happy path" + test "encode and decode record with multiaddrs field (EnrBuilder ext)": + ## Given let - enrIp = ValidIpAddress.init("127.0.0.1") - enrTcpPort, enrUdpPort = Port(61101) - enrKey = generateSecp256k1Key() - multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[], - MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]] + enrSeqNum = 1u64 + enrPrivKey = generatesecp256k1key() let - record = enr.Record.init(1, enrKey, some(enrIp), - some(enrTcpPort), some(enrUdpPort), - none(CapabilitiesBitfield), - multiaddrs) - typedRecord = record.toTyped().get() + addr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws").get() + addr2 = MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss").get() - # Check EIP-778 ENR fields + ## When + var builder = EnrBuilder.init(enrPrivKey, seqNum = enrSeqNum) + builder.withMultiaddrs(addr1, addr2) + + let recordRes = builder.build() + + require recordRes.isOk() + let record = recordRes.tryGet() + + let typedRecord = record.toTyped() + require typedRecord.isOk() + + let multiaddrsOpt = typedRecord.value.multiaddrs + + ## Then + check multiaddrsOpt.isSome() + + let multiaddrs = multiaddrsOpt.get() check: - @(typedRecord.secp256k1.get()) == enrKey.getPublicKey()[].getRawBytes()[] - ipv4(typedRecord.ip.get()) == enrIp - Port(typedRecord.tcp.get()) == enrTcpPort - Port(typedRecord.udp.get()) == enrUdpPort + multiaddrs.len == 2 + multiaddrs.contains(addr1) + multiaddrs.contains(addr2) - # Check Waku ENR fields - let decodedAddrs = record.get(MultiaddrEnrField, seq[byte]).tryGet().toMultiAddresses() + test "encode and decode record with multiaddrs field (deprecated)": + # TODO: Remove after removing the `Record.init()` proc + ## Given + let enrkey = generatesecp256k1key() + let caps = CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store) + + let + addr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws").get() + addr2 = MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss").get() + + ## When + let record = Record.init(1, enrkey, wakuFlags=some(caps), multiaddrs = @[addr1, addr2]) + + let typedRecord = record.toTyped() + require typedRecord.isOk() + + let multiaddrsOpt = typedRecord.value.multiaddrs + + ## Then + check multiaddrsOpt.isSome() + + let multiaddrs = multiaddrsOpt.get() check: - decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/442/ws")[]) - decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]) + multiaddrs.contains(addr1) + multiaddrs.contains(addr2) - test "Strip multiaddr peerId": - # Tests that peerId is stripped of multiaddrs as per RFC31 + test "cannot decode multiaddresses from record": + ## Given let - enrIp = ValidIpAddress.init("127.0.0.1") - enrTcpPort, enrUdpPort = Port(61102) - enrKey = generateSecp256k1Key() - multiaddrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD")[]] + enrSeqNum = 1u64 + enrPrivKey = generatesecp256k1key() + + let record = EnrBuilder.init(enrPrivKey, enrSeqNum).build().tryGet() + + ## When + let typedRecord = record.toTyped() + require typedRecord.isOk() + + let fieldOpt = typedRecord.value.multiaddrs + + ## Then + check fieldOpt.isNone() + + test "encode and decode record with multiaddresses field - strip peer ID": + ## Given + let + enrSeqNum = 1u64 + enrPrivKey = generatesecp256k1key() let - record = enr.Record.init(1, enrKey, some(enrIp), - some(enrTcpPort), some(enrUdpPort), - none(CapabilitiesBitfield), - multiaddrs) + addr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr31iDQpSN5Qa882BCjjwgrD").get() + addr2 = MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss").get() - # Check Waku ENR fields - let - decodedAddrs = record.get(MULTIADDR_ENR_FIELD, seq[byte])[].toMultiAddresses() + let expectedAddr1 = MultiAddress.init("/ip4/127.0.0.1/tcp/80/ws").get() - check decodedAddrs.contains(MultiAddress.init("/ip4/127.0.0.1/tcp/443/wss")[]) # Peer Id has been stripped + ## When + var builder = EnrBuilder.init(enrPrivKey, seqNum = enrSeqNum) + builder.withMultiaddrs(addr1, addr2) - test "Decode ENR with multiaddrs field": - let - # Known values correspond to shared test vectors with other Waku implementations - knownIp = ValidIpAddress.init("18.223.219.100") - knownUdpPort = some(9000.uint16) - knownTcpPort = none(uint16) - knownMultiaddrs = @[MultiAddress.init("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss")[], - MultiAddress.init("/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss")[]] - knownEnr = "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSH" & - "KCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcn" & - "O4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG" & - "73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1" & - "c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1" & - "-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA" + let recordRes = builder.build() - var enrRecord: Record + require recordRes.isOk() + let record = recordRes.tryGet() + + let typedRecord = record.toTyped() + require typedRecord.isOk() + + let multiaddrsOpt = typedRecord.value.multiaddrs + + ## Then + check multiaddrsOpt.isSome() + + let multiaddrs = multiaddrsOpt.get() check: - enrRecord.fromURI(knownEnr) - - let typedRecord = enrRecord.toTyped().get() - - # Check EIP-778 ENR fields - check: - 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) + multiaddrs.contains(expectedAddr1) + multiaddrs.contains(addr2) diff --git a/tests/v2/test_waku_peer_exchange.nim b/tests/v2/test_waku_peer_exchange.nim index 96e80880f..761be0f19 100644 --- a/tests/v2/test_waku_peer_exchange.nim +++ b/tests/v2/test_waku_peer_exchange.nim @@ -101,7 +101,7 @@ procSuite "Waku Peer Exchange": some(extIp), some(nodeTcpPort1), some(nodeUdpPort1), bindIp, nodeUdpPort1, - newSeq[string](), + newSeq[enr.Record](), false, keys.PrivateKey(nodeKey1.skkey), flags, @@ -113,7 +113,7 @@ procSuite "Waku Peer Exchange": some(extIp), some(nodeTcpPort2), some(nodeUdpPort2), bindIp, nodeUdpPort2, - @[node1.wakuDiscv5.protocol.localNode.record.toURI()], # Bootstrap with node1 + @[node1.wakuDiscv5.protocol.localNode.record], # Bootstrap with node1 false, keys.PrivateKey(nodeKey2.skkey), flags, diff --git a/waku/v2/protocol/waku_discv5.nim b/waku/v2/protocol/waku_discv5.nim index da1b1f349..5d42d2eff 100644 --- a/waku/v2/protocol/waku_discv5.nim +++ b/waku/v2/protocol/waku_discv5.nim @@ -27,8 +27,7 @@ logScope: topics = "waku discv5" -type - WakuDiscoveryV5* = ref object +type WakuDiscoveryV5* = ref object protocol*: protocol.Protocol listening*: bool @@ -65,52 +64,21 @@ proc addBootstrapNode*(bootstrapAddr: string, return let enrRes = parseBootstrapAddress(bootstrapAddr) - if enrRes.isOk(): - bootstrapEnrs.add(enrRes.value) - else: - warn "Ignoring invalid bootstrap address", - bootstrapAddr, reason = enrRes.error + if enrRes.isErr(): + debug "ignoring invalid bootstrap address", reason = enrRes.error + return -proc isWakuNode(node: Node): bool = - let wakuField = node.record.tryGet(CapabilitiesEnrField, uint8) + bootstrapEnrs.add(enrRes.value) - if wakuField.isSome(): - return wakuField.get() != 0x00 # True if any flag set to true - - return false #################### # Discovery v5 API # #################### -proc findRandomPeers*(wakuDiscv5: 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 wakuDiscv5.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: - # Convert discovered ENR to RemotePeerInfo and add to discovered nodes - let res = node.record.toRemotePeerInfo() - - if res.isOk(): - discoveredPeers.add(res.get()) - else: - error "Failed to convert ENR to peer info", enr= $node.record, err=res.error - waku_discv5_errors.inc(labelValues = ["peer_info_failure"]) - - - return ok(discoveredPeers) - proc new*(T: type WakuDiscoveryV5, extIp: Option[ValidIpAddress], - extTcpPort, extUdpPort: Option[Port], + extTcpPort: Option[Port], + extUdpPort: Option[Port], bindIP: ValidIpAddress, discv5UdpPort: Port, bootstrapEnrs = newSeq[enr.Record](), @@ -120,73 +88,72 @@ proc new*(T: type WakuDiscoveryV5, 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 + # Add the waku capabilities field var enrInitFields = @[(CapabilitiesEnrField, @[flags.byte])] - ## Add multiaddresses to ENR + # Add the waku multiaddrs field if multiaddrs.len > 0: - enrInitFields.add((MULTIADDR_ENR_FIELD, multiaddrs.getRawField())) + let value = waku_enr.encodeMultiaddrs(multiaddrs) + enrInitFields.add((MultiaddrEnrField, value)) let protocol = newProtocol( privateKey, - enrIp = extIp, enrTcpPort = extTcpPort, enrUdpPort = extUdpPort, # We use the external IP & ports for ENR + enrIp = extIp, + enrTcpPort = extTcpPort, + enrUdpPort = extUdpPort, enrInitFields, bootstrapEnrs, bindPort = discv5UdpPort, bindIp = bindIP, enrAutoUpdate = enrAutoUpdate, config = discv5Config, - rng = rng) + rng = rng + ) - return WakuDiscoveryV5(protocol: protocol, listening: false) + WakuDiscoveryV5(protocol: protocol, listening: false) -# constructor that takes bootstrap Enrs in Enr Uri form -proc new*(T: type WakuDiscoveryV5, - extIp: Option[ValidIpAddress], - extTcpPort, extUdpPort: Option[Port], - bindIP: ValidIpAddress, - discv5UdpPort: Port, - bootstrapNodes: seq[string], - enrAutoUpdate = false, - privateKey: keys.PrivateKey, - flags: CapabilitiesBitfield, - multiaddrs = newSeq[MultiAddress](), - rng: ref HmacDrbgContext, - discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T = - - var bootstrapEnrs: seq[enr.Record] - for node in bootstrapNodes: - addBootstrapNode(node, bootstrapEnrs) - - return WakuDiscoveryV5.new( - extIP, extTcpPort, extUdpPort, - bindIP, - discv5UdpPort, - bootstrapEnrs, - enrAutoUpdate, - privateKey, - flags, - multiaddrs, - rng, - discv5Config - ) - - -proc open*(wakuDiscv5: WakuDiscoveryV5) {.raises: [CatchableError].} = +# 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 - wakuDiscv5.protocol.open() - wakuDiscv5.listening = true + wd.protocol.open() + wd.listening = true -proc start*(wakuDiscv5: WakuDiscoveryV5) = - debug "Starting Waku discovery v5 service" +proc start*(wd: WakuDiscoveryV5) = + debug "starting Waku discovery v5 service" + wd.protocol.start() - wakuDiscv5.protocol.start() +proc closeWait*(wd: WakuDiscoveryV5) {.async.} = + debug "closing Waku discovery v5 node" + if not wd.listening: + return -proc closeWait*(wakuDiscv5: WakuDiscoveryV5) {.async.} = - debug "Closing Waku discovery v5 node" + wd.listening = false + await wd.protocol.closeWait() - wakuDiscv5.listening = false - await wakuDiscv5.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) diff --git a/waku/v2/protocol/waku_enr.nim b/waku/v2/protocol/waku_enr.nim index 1d9697ae1..3b8e685c6 100644 --- a/waku/v2/protocol/waku_enr.nim +++ b/waku/v2/protocol/waku_enr.nim @@ -115,47 +115,17 @@ proc getCapabilities*(r: Record): seq[Capabilities] = ## Multiaddress -func getRawField*(multiaddrs: seq[MultiAddress]): seq[byte] = - var fieldRaw: seq[byte] - +func encodeMultiaddrs*(multiaddrs: seq[MultiAddress]): seq[byte] = + var buffer = newSeq[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 + raw = multiaddr.data.buffer # binary encoded multiaddr + size = raw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer - assert maSize.len == 2 + buffer.add(concat(@size, raw)) - 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 = - var cleanAddr = MultiAddress.init() - - for item in multiaddr.items: - if item[].protoName()[] != "p2p": - # Add all parts except p2p peerId - discard cleanAddr.append(item[]) - - return cleanAddr - -func stripPeerIds*(multiaddrs: seq[MultiAddress]): seq[MultiAddress] = - var cleanAddrs: seq[MultiAddress] - - for multiaddr in multiaddrs: - if multiaddr.contains(multiCodec("p2p"))[]: - cleanAddrs.add(multiaddr.stripPeerId()) - else: - cleanAddrs.add(multiaddr) - - return cleanAddrs + buffer func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] = ## Attempts to read `numBytes` from a sequence, from @@ -163,46 +133,71 @@ func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq ## 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") + return err("insufficient bytes") let slicedSeq = rawBytes[pos.. 0: - wakuEnrFields.add(multiaddrs.stripPeerIds().toFieldPair()) + let value = encodeMultiaddrs(multiaddrs) + wakuEnrFields.add(toFieldPair(MultiaddrEnrField, value)) let rawPk = privateKey.getRawBytes().expect("Private key is valid")