2022-11-04 09:52:27 +00:00
|
|
|
when (NimMajor, NimMinor) < (1, 4):
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
else:
|
|
|
|
{.push raises: [].}
|
2021-11-01 18:02:39 +00:00
|
|
|
|
|
|
|
import
|
2022-06-28 23:59:38 +00:00
|
|
|
std/[strutils, options],
|
2021-11-01 18:02:39 +00:00
|
|
|
chronos, chronicles, metrics,
|
|
|
|
eth/keys,
|
2021-11-12 14:10:54 +00:00
|
|
|
eth/p2p/discoveryv5/[enr, node, protocol],
|
2021-11-01 18:02:39 +00:00
|
|
|
stew/results,
|
2022-10-18 17:35:26 +00:00
|
|
|
../../../../apps/wakunode2/config, ## TODO: Remove dependency on wakunode2 config module
|
2021-12-06 19:51:37 +00:00
|
|
|
../../utils/[peers, wakuenr]
|
2021-11-01 18:02:39 +00:00
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
export protocol, wakuenr
|
2021-11-01 18:02:39 +00:00
|
|
|
|
|
|
|
declarePublicGauge waku_discv5_discovered, "number of nodes discovered"
|
|
|
|
declarePublicGauge waku_discv5_errors, "number of waku discv5 errors", ["type"]
|
|
|
|
|
|
|
|
logScope:
|
2022-11-03 15:36:24 +00:00
|
|
|
topics = "waku discv5"
|
2021-11-01 18:02:39 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
WakuDiscoveryV5* = ref object
|
|
|
|
protocol*: protocol.Protocol
|
|
|
|
listening*: bool
|
|
|
|
|
2021-11-12 14:10:54 +00:00
|
|
|
####################
|
|
|
|
# Helper functions #
|
|
|
|
####################
|
|
|
|
|
2022-11-02 13:55:48 +00:00
|
|
|
proc parseBootstrapAddress(address: string):
|
2021-11-01 18:02:39 +00:00
|
|
|
Result[enr.Record, cstring] =
|
|
|
|
logScope:
|
|
|
|
address = string(address)
|
|
|
|
|
|
|
|
if address[0] == '/':
|
|
|
|
return err "MultiAddress bootstrap addresses are not supported"
|
|
|
|
else:
|
|
|
|
let lowerCaseAddress = toLowerAscii(string address)
|
|
|
|
if lowerCaseAddress.startsWith("enr:"):
|
|
|
|
var enrRec: enr.Record
|
|
|
|
if enrRec.fromURI(string address):
|
|
|
|
return ok enrRec
|
|
|
|
return err "Invalid ENR bootstrap record"
|
|
|
|
elif lowerCaseAddress.startsWith("enode:"):
|
|
|
|
return err "ENode bootstrap addresses are not supported"
|
|
|
|
else:
|
|
|
|
return err "Ignoring unrecognized bootstrap address type"
|
|
|
|
|
2022-03-17 16:33:17 +00:00
|
|
|
proc addBootstrapNode*(bootstrapAddr: string,
|
2021-11-01 18:02:39 +00:00
|
|
|
bootstrapEnrs: var seq[enr.Record]) =
|
|
|
|
# Ignore empty lines or lines starting with #
|
|
|
|
if bootstrapAddr.len == 0 or bootstrapAddr[0] == '#':
|
|
|
|
return
|
|
|
|
|
|
|
|
let enrRes = parseBootstrapAddress(bootstrapAddr)
|
|
|
|
if enrRes.isOk:
|
|
|
|
bootstrapEnrs.add enrRes.value
|
|
|
|
else:
|
|
|
|
warn "Ignoring invalid bootstrap address",
|
|
|
|
bootstrapAddr, reason = enrRes.error
|
|
|
|
|
2021-11-12 14:10:54 +00:00
|
|
|
proc isWakuNode(node: Node): bool =
|
|
|
|
let wakuField = node.record.tryGet(WAKU_ENR_FIELD, uint8)
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2021-11-12 14:10:54 +00:00
|
|
|
if wakuField.isSome:
|
|
|
|
return wakuField.get().WakuEnrBitfield != 0x00 # True if any flag set to true
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
2021-11-01 18:02:39 +00:00
|
|
|
####################
|
|
|
|
# Discovery v5 API #
|
|
|
|
####################
|
|
|
|
|
|
|
|
proc findRandomPeers*(wakuDiscv5: WakuDiscoveryV5): Future[Result[seq[RemotePeerInfo], cstring]] {.async.} =
|
|
|
|
## Find random peers to connect to using Discovery v5
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2021-11-01 18:02:39 +00:00
|
|
|
## Query for a random target and collect all discovered nodes
|
|
|
|
let discoveredNodes = await wakuDiscv5.protocol.queryRandom()
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2021-11-12 14:10:54 +00:00
|
|
|
## Filter based on our needs
|
2022-03-01 14:11:56 +00:00
|
|
|
# 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
|
2021-11-12 14:10:54 +00:00
|
|
|
|
2021-11-01 18:02:39 +00:00
|
|
|
var discoveredPeers: seq[RemotePeerInfo]
|
|
|
|
|
2022-03-01 14:11:56 +00:00
|
|
|
for node in discoveredNodes:
|
2021-11-01 18:02:39 +00:00
|
|
|
# Convert discovered ENR to RemotePeerInfo and add to discovered nodes
|
|
|
|
let res = node.record.toRemotePeerInfo()
|
|
|
|
|
|
|
|
if res.isOk():
|
|
|
|
discoveredPeers.add(res.get())
|
|
|
|
else:
|
2022-12-07 11:30:32 +00:00
|
|
|
error "Failed to convert ENR to peer info", enr= $node.record, err=res.error()
|
2021-11-01 18:02:39 +00:00
|
|
|
waku_discv5_errors.inc(labelValues = ["peer_info_failure"])
|
|
|
|
|
|
|
|
|
|
|
|
return ok(discoveredPeers)
|
|
|
|
|
|
|
|
proc new*(T: type WakuDiscoveryV5,
|
|
|
|
extIp: Option[ValidIpAddress],
|
|
|
|
extTcpPort, extUdpPort: Option[Port],
|
|
|
|
bindIP: ValidIpAddress,
|
|
|
|
discv5UdpPort: Port,
|
2022-03-17 16:33:17 +00:00
|
|
|
bootstrapEnrs: seq[enr.Record],
|
2021-11-01 18:02:39 +00:00
|
|
|
enrAutoUpdate = false,
|
2021-12-06 19:51:37 +00:00
|
|
|
privateKey: keys.PrivateKey,
|
2021-11-12 14:10:54 +00:00
|
|
|
flags: WakuEnrBitfield,
|
2021-11-01 18:02:39 +00:00
|
|
|
enrFields: openArray[(string, seq[byte])],
|
2022-09-07 15:31:27 +00:00
|
|
|
rng: ref HmacDrbgContext,
|
2022-03-01 14:11:56 +00:00
|
|
|
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
|
2021-11-01 18:02:39 +00:00
|
|
|
## TODO: consider loading from a configurable bootstrap file
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2021-11-12 14:10:54 +00:00
|
|
|
## We always add the waku field as specified
|
|
|
|
var enrInitFields = @[(WAKU_ENR_FIELD, @[flags.byte])]
|
|
|
|
enrInitFields.add(enrFields)
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2021-11-01 18:02:39 +00:00
|
|
|
let protocol = newProtocol(
|
|
|
|
privateKey,
|
|
|
|
enrIp = extIp, enrTcpPort = extTcpPort, enrUdpPort = extUdpPort, # We use the external IP & ports for ENR
|
2021-11-12 14:10:54 +00:00
|
|
|
enrInitFields,
|
2021-11-01 18:02:39 +00:00
|
|
|
bootstrapEnrs,
|
|
|
|
bindPort = discv5UdpPort,
|
|
|
|
bindIp = bindIP,
|
|
|
|
enrAutoUpdate = enrAutoUpdate,
|
2022-03-01 14:11:56 +00:00
|
|
|
config = discv5Config,
|
2021-11-01 18:02:39 +00:00
|
|
|
rng = rng)
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2021-11-01 18:02:39 +00:00
|
|
|
return WakuDiscoveryV5(protocol: protocol, listening: false)
|
|
|
|
|
2022-03-17 16:33:17 +00:00
|
|
|
# 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: WakuEnrBitfield,
|
|
|
|
enrFields: openArray[(string, seq[byte])],
|
2022-09-07 15:31:27 +00:00
|
|
|
rng: ref HmacDrbgContext,
|
2022-03-17 16:33:17 +00:00
|
|
|
discv5Config: protocol.DiscoveryConfig = protocol.defaultDiscoveryConfig): T =
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2022-03-17 16:33:17 +00:00
|
|
|
var bootstrapEnrs: seq[enr.Record]
|
|
|
|
for node in bootstrapNodes:
|
|
|
|
addBootstrapNode(node, bootstrapEnrs)
|
2022-12-07 11:30:32 +00:00
|
|
|
|
2022-03-17 16:33:17 +00:00
|
|
|
return WakuDiscoveryV5.new(
|
|
|
|
extIP, extTcpPort, extUdpPort,
|
|
|
|
bindIP,
|
|
|
|
discv5UdpPort,
|
|
|
|
bootstrapEnrs,
|
|
|
|
enrAutoUpdate,
|
|
|
|
privateKey,
|
|
|
|
flags,
|
|
|
|
enrFields,
|
|
|
|
rng,
|
|
|
|
discv5Config
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-11-01 18:02:39 +00:00
|
|
|
proc open*(wakuDiscv5: WakuDiscoveryV5) {.raises: [Defect, CatchableError].} =
|
|
|
|
debug "Opening Waku discovery v5 ports"
|
|
|
|
|
|
|
|
wakuDiscv5.protocol.open()
|
|
|
|
wakuDiscv5.listening = true
|
|
|
|
|
|
|
|
proc start*(wakuDiscv5: WakuDiscoveryV5) =
|
|
|
|
debug "Starting Waku discovery v5 service"
|
|
|
|
|
|
|
|
wakuDiscv5.protocol.start()
|
|
|
|
|
|
|
|
proc closeWait*(wakuDiscv5: WakuDiscoveryV5) {.async.} =
|
|
|
|
debug "Closing Waku discovery v5 node"
|
|
|
|
|
|
|
|
wakuDiscv5.listening = false
|
|
|
|
await wakuDiscv5.protocol.closeWait()
|