2022-10-11 03:58:44 +00:00
|
|
|
import
|
|
|
|
std/[strutils, sequtils, tables],
|
|
|
|
confutils,
|
|
|
|
chronos,
|
|
|
|
stew/shims/net,
|
2024-02-12 14:28:22 +00:00
|
|
|
chronicles/topics_registry,
|
|
|
|
os
|
2022-10-11 03:58:44 +00:00
|
|
|
import
|
|
|
|
libp2p/protocols/ping,
|
2022-10-18 15:13:42 +00:00
|
|
|
libp2p/crypto/[crypto, secp],
|
2023-04-11 15:05:12 +00:00
|
|
|
libp2p/nameresolving/dnsresolver,
|
|
|
|
libp2p/multicodec
|
2022-10-11 03:58:44 +00:00
|
|
|
import
|
2024-02-12 14:28:22 +00:00
|
|
|
./certsgenerator,
|
2024-07-05 22:03:38 +00:00
|
|
|
waku/[waku_enr, node/peer_manager, waku_core, waku_node, factory/builder]
|
2022-10-11 03:58:44 +00:00
|
|
|
|
|
|
|
# protocols and their tag
|
|
|
|
const ProtocolsTable = {
|
|
|
|
"store": "/vac/waku/store/",
|
2024-05-27 21:13:59 +00:00
|
|
|
"storev3": "/vac/waku/store-query/3",
|
2022-10-11 03:58:44 +00:00
|
|
|
"relay": "/vac/waku/relay/",
|
|
|
|
"lightpush": "/vac/waku/lightpush/",
|
2024-05-27 21:13:59 +00:00
|
|
|
"filter": "/vac/waku/filter-subscribe/2",
|
2022-10-11 03:58:44 +00:00
|
|
|
}.toTable
|
|
|
|
|
2023-04-11 15:05:12 +00:00
|
|
|
const WebSocketPortOffset = 1000
|
2024-02-12 14:28:22 +00:00
|
|
|
const CertsDirectory = "./certs"
|
2023-04-11 15:05:12 +00:00
|
|
|
|
2022-10-11 03:58:44 +00:00
|
|
|
# cli flags
|
2024-03-15 23:08:47 +00:00
|
|
|
type WakuCanaryConf* = object
|
|
|
|
address* {.
|
|
|
|
desc: "Multiaddress of the peer node to attempt to dial",
|
|
|
|
defaultValue: "",
|
|
|
|
name: "address",
|
|
|
|
abbr: "a"
|
|
|
|
.}: string
|
|
|
|
|
|
|
|
timeout* {.
|
|
|
|
desc: "Timeout to consider that the connection failed",
|
|
|
|
defaultValue: chronos.seconds(10),
|
|
|
|
name: "timeout",
|
|
|
|
abbr: "t"
|
|
|
|
.}: chronos.Duration
|
|
|
|
|
|
|
|
protocols* {.
|
|
|
|
desc:
|
|
|
|
"Protocol required to be supported: store,relay,lightpush,filter (can be used multiple times)",
|
|
|
|
name: "protocol",
|
|
|
|
abbr: "p"
|
|
|
|
.}: seq[string]
|
|
|
|
|
|
|
|
logLevel* {.
|
|
|
|
desc: "Sets the log level",
|
|
|
|
defaultValue: LogLevel.INFO,
|
|
|
|
name: "log-level",
|
|
|
|
abbr: "l"
|
|
|
|
.}: LogLevel
|
|
|
|
|
|
|
|
nodePort* {.
|
|
|
|
desc: "Listening port for waku node",
|
|
|
|
defaultValue: 60000,
|
|
|
|
name: "node-port",
|
|
|
|
abbr: "np"
|
|
|
|
.}: uint16
|
|
|
|
|
|
|
|
## websocket secure config
|
|
|
|
websocketSecureKeyPath* {.
|
|
|
|
desc: "Secure websocket key path: '/path/to/key.txt' ",
|
|
|
|
defaultValue: "",
|
|
|
|
name: "websocket-secure-key-path"
|
|
|
|
.}: string
|
|
|
|
|
|
|
|
websocketSecureCertPath* {.
|
|
|
|
desc: "Secure websocket Certificate path: '/path/to/cert.txt' ",
|
|
|
|
defaultValue: "",
|
|
|
|
name: "websocket-secure-cert-path"
|
|
|
|
.}: string
|
|
|
|
|
|
|
|
ping* {.
|
|
|
|
desc: "Ping the peer node to measure latency", defaultValue: true, name: "ping"
|
|
|
|
.}: bool
|
2023-09-26 13:02:12 +00:00
|
|
|
|
2024-06-14 12:59:42 +00:00
|
|
|
shards* {.
|
2024-09-10 21:07:12 +00:00
|
|
|
desc:
|
|
|
|
"Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated.",
|
2024-06-14 12:59:42 +00:00
|
|
|
defaultValue: @[],
|
|
|
|
name: "shard",
|
|
|
|
abbr: "s"
|
|
|
|
.}: seq[uint16]
|
|
|
|
|
|
|
|
clusterId* {.
|
|
|
|
desc:
|
|
|
|
"Cluster id that the node is running in. Node in a different cluster id is disconnected.",
|
|
|
|
defaultValue: 1,
|
|
|
|
name: "cluster-id",
|
|
|
|
abbr: "c"
|
|
|
|
.}: uint16
|
|
|
|
|
2022-11-02 13:55:48 +00:00
|
|
|
proc parseCmdArg*(T: type chronos.Duration, p: string): T =
|
2022-10-11 03:58:44 +00:00
|
|
|
try:
|
2023-04-11 15:05:12 +00:00
|
|
|
result = chronos.seconds(parseInt(p))
|
2023-02-07 09:45:25 +00:00
|
|
|
except CatchableError:
|
2023-09-21 11:12:14 +00:00
|
|
|
raise newException(ValueError, "Invalid timeout value")
|
2022-10-11 03:58:44 +00:00
|
|
|
|
2022-11-02 13:55:48 +00:00
|
|
|
proc completeCmdArg*(T: type chronos.Duration, val: string): seq[string] =
|
2022-10-11 03:58:44 +00:00
|
|
|
return @[]
|
|
|
|
|
|
|
|
# checks if rawProtocols (skipping version) are supported in nodeProtocols
|
|
|
|
proc areProtocolsSupported(
|
2024-03-15 23:08:47 +00:00
|
|
|
rawProtocols: seq[string], nodeProtocols: seq[string]
|
|
|
|
): bool =
|
2022-10-11 03:58:44 +00:00
|
|
|
var numOfSupportedProt: int = 0
|
|
|
|
|
|
|
|
for nodeProtocol in nodeProtocols:
|
|
|
|
for rawProtocol in rawProtocols:
|
|
|
|
let protocolTag = ProtocolsTable[rawProtocol]
|
|
|
|
if nodeProtocol.startsWith(protocolTag):
|
2024-03-15 23:08:47 +00:00
|
|
|
info "Supported protocol ok", expected = protocolTag, supported = nodeProtocol
|
2022-10-11 03:58:44 +00:00
|
|
|
numOfSupportedProt += 1
|
|
|
|
break
|
|
|
|
|
|
|
|
if numOfSupportedProt == rawProtocols.len:
|
|
|
|
return true
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
proc pingNode(
|
|
|
|
node: WakuNode, peerInfo: RemotePeerInfo
|
|
|
|
): Future[void] {.async, gcsafe.} =
|
2023-09-26 13:02:12 +00:00
|
|
|
try:
|
|
|
|
let conn = await node.switch.dial(peerInfo.peerId, peerInfo.addrs, PingCodec)
|
|
|
|
let pingDelay = await node.libp2pPing.ping(conn)
|
2024-03-15 23:08:47 +00:00
|
|
|
info "Peer response time (ms)", peerId = peerInfo.peerId, ping = pingDelay.millis
|
2023-09-26 13:02:12 +00:00
|
|
|
except CatchableError:
|
|
|
|
var msg = getCurrentExceptionMsg()
|
|
|
|
if msg == "Future operation cancelled!":
|
|
|
|
msg = "timedout"
|
2024-03-15 23:08:47 +00:00
|
|
|
error "Failed to ping the peer", peer = peerInfo, err = msg
|
2023-09-26 13:02:12 +00:00
|
|
|
|
2023-02-06 11:53:05 +00:00
|
|
|
proc main(rng: ref HmacDrbgContext): Future[int] {.async.} =
|
2022-10-11 03:58:44 +00:00
|
|
|
let conf: WakuCanaryConf = WakuCanaryConf.load()
|
|
|
|
|
2022-10-18 15:13:42 +00:00
|
|
|
# create dns resolver
|
|
|
|
let
|
2024-03-15 23:08:47 +00:00
|
|
|
nameServers =
|
|
|
|
@[
|
|
|
|
initTAddress(parseIpAddress("1.1.1.1"), Port(53)),
|
|
|
|
initTAddress(parseIpAddress("1.0.0.1"), Port(53)),
|
|
|
|
]
|
2022-10-18 15:13:42 +00:00
|
|
|
resolver: DnsResolver = DnsResolver.new(nameServers)
|
|
|
|
|
2022-10-11 03:58:44 +00:00
|
|
|
if conf.logLevel != LogLevel.NONE:
|
|
|
|
setLogLevel(conf.logLevel)
|
|
|
|
|
|
|
|
# ensure input protocols are valid
|
|
|
|
for p in conf.protocols:
|
2022-11-24 13:11:23 +00:00
|
|
|
if p notin ProtocolsTable:
|
2023-04-11 15:05:12 +00:00
|
|
|
error "invalid protocol", protocol = p, valid = ProtocolsTable
|
2022-10-11 03:58:44 +00:00
|
|
|
raise newException(ConfigurationError, "Invalid cli flag values" & p)
|
|
|
|
|
|
|
|
info "Cli flags",
|
2023-04-11 15:05:12 +00:00
|
|
|
address = conf.address,
|
|
|
|
timeout = conf.timeout,
|
|
|
|
protocols = conf.protocols,
|
|
|
|
logLevel = conf.logLevel
|
2022-10-11 03:58:44 +00:00
|
|
|
|
2023-04-12 09:29:11 +00:00
|
|
|
let peerRes = parsePeerInfo(conf.address)
|
|
|
|
if peerRes.isErr():
|
|
|
|
error "Couldn't parse 'conf.address'", error = peerRes.error
|
|
|
|
return 1
|
|
|
|
|
|
|
|
let peer = peerRes.value
|
|
|
|
|
2022-10-11 03:58:44 +00:00
|
|
|
let
|
|
|
|
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
2023-12-14 06:16:39 +00:00
|
|
|
bindIp = parseIpAddress("0.0.0.0")
|
2023-04-11 15:05:12 +00:00
|
|
|
wsBindPort = Port(conf.nodePort + WebSocketPortOffset)
|
2023-04-05 12:27:11 +00:00
|
|
|
nodeTcpPort = Port(conf.nodePort)
|
2023-04-11 15:05:12 +00:00
|
|
|
isWs = peer.addrs[0].contains(multiCodec("ws")).get()
|
|
|
|
isWss = peer.addrs[0].contains(multiCodec("wss")).get()
|
2024-03-15 23:08:47 +00:00
|
|
|
keyPath =
|
|
|
|
if conf.websocketSecureKeyPath.len > 0:
|
|
|
|
conf.websocketSecureKeyPath
|
|
|
|
else:
|
|
|
|
CertsDirectory & "/key.pem"
|
|
|
|
certPath =
|
|
|
|
if conf.websocketSecureCertPath.len > 0:
|
|
|
|
conf.websocketSecureCertPath
|
|
|
|
else:
|
|
|
|
CertsDirectory & "/cert.pem"
|
2023-04-05 12:27:11 +00:00
|
|
|
|
|
|
|
var builder = WakuNodeBuilder.init()
|
|
|
|
builder.withNodeKey(nodeKey)
|
2023-04-11 15:05:12 +00:00
|
|
|
|
|
|
|
let netConfig = NetConfig.init(
|
|
|
|
bindIp = bindIp,
|
|
|
|
bindPort = nodeTcpPort,
|
|
|
|
wsBindPort = wsBindPort,
|
|
|
|
wsEnabled = isWs,
|
|
|
|
wssEnabled = isWss,
|
|
|
|
)
|
|
|
|
|
2023-08-04 05:22:31 +00:00
|
|
|
var enrBuilder = EnrBuilder.init(nodeKey)
|
|
|
|
|
2024-06-14 12:59:42 +00:00
|
|
|
let relayShards = RelayShards.init(conf.clusterId, conf.shards).valueOr:
|
|
|
|
error "Relay shards initialization failed", error = error
|
|
|
|
return 1
|
|
|
|
enrBuilder.withWakuRelaySharding(relayShards).isOkOr:
|
|
|
|
error "Building ENR with relay sharding failed", error = error
|
|
|
|
return 1
|
|
|
|
|
2023-08-04 05:22:31 +00:00
|
|
|
let recordRes = enrBuilder.build()
|
|
|
|
let record =
|
|
|
|
if recordRes.isErr():
|
2024-03-15 23:08:47 +00:00
|
|
|
error "failed to create enr record", error = recordRes.error
|
2023-08-04 05:22:31 +00:00
|
|
|
quit(QuitFailure)
|
2024-03-15 23:08:47 +00:00
|
|
|
else:
|
|
|
|
recordRes.get()
|
2023-08-04 05:22:31 +00:00
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
if isWss and
|
|
|
|
(conf.websocketSecureKeyPath.len == 0 or conf.websocketSecureCertPath.len == 0):
|
2024-02-12 14:28:22 +00:00
|
|
|
info "WebSocket Secure requires key and certificate. Generating them"
|
|
|
|
if not dirExists(CertsDirectory):
|
|
|
|
createDir(CertsDirectory)
|
|
|
|
if generateSelfSignedCertificate(certPath, keyPath) != 0:
|
|
|
|
error "Error generating key and certificate"
|
|
|
|
return 1
|
2023-04-11 15:05:12 +00:00
|
|
|
|
2023-08-04 05:22:31 +00:00
|
|
|
builder.withRecord(record)
|
2023-04-11 15:05:12 +00:00
|
|
|
builder.withNetworkConfiguration(netConfig.tryGet())
|
|
|
|
builder.withSwitchConfiguration(
|
2024-03-15 23:08:47 +00:00
|
|
|
secureKey = some(keyPath), secureCert = some(certPath), nameResolver = resolver
|
2023-04-11 15:05:12 +00:00
|
|
|
)
|
|
|
|
|
2023-04-05 12:27:11 +00:00
|
|
|
let node = builder.build().tryGet()
|
2024-06-14 12:59:42 +00:00
|
|
|
node.mountMetadata(conf.clusterId).isOkOr:
|
|
|
|
error "failed to mount waku metadata protocol: ", err = error
|
2022-10-11 03:58:44 +00:00
|
|
|
|
2023-09-26 13:02:12 +00:00
|
|
|
if conf.ping:
|
|
|
|
try:
|
|
|
|
await mountLibp2pPing(node)
|
|
|
|
except CatchableError:
|
|
|
|
error "failed to mount libp2p ping protocol: " & getCurrentExceptionMsg()
|
|
|
|
return 1
|
|
|
|
|
2022-10-11 03:58:44 +00:00
|
|
|
await node.start()
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
var pingFut: Future[bool]
|
2023-09-26 13:02:12 +00:00
|
|
|
if conf.ping:
|
|
|
|
pingFut = pingNode(node, peer).withTimeout(conf.timeout)
|
|
|
|
|
2022-10-11 03:58:44 +00:00
|
|
|
let timedOut = not await node.connectToNodes(@[peer]).withTimeout(conf.timeout)
|
|
|
|
if timedOut:
|
2023-04-11 15:05:12 +00:00
|
|
|
error "Timedout after", timeout = conf.timeout
|
2023-08-11 08:00:19 +00:00
|
|
|
return 1
|
2022-10-11 03:58:44 +00:00
|
|
|
|
|
|
|
let lp2pPeerStore = node.switch.peerStore
|
2024-09-27 12:46:46 +00:00
|
|
|
let conStatus = node.peerManager.wakuPeerStore[ConnectionBook][peer.peerId]
|
2022-10-11 03:58:44 +00:00
|
|
|
|
2023-09-26 13:02:12 +00:00
|
|
|
if conf.ping:
|
|
|
|
discard await pingFut
|
|
|
|
|
2022-10-11 03:58:44 +00:00
|
|
|
if conStatus in [Connected, CanConnect]:
|
|
|
|
let nodeProtocols = lp2pPeerStore[ProtoBook][peer.peerId]
|
|
|
|
if not areProtocolsSupported(conf.protocols, nodeProtocols):
|
2024-03-15 23:08:47 +00:00
|
|
|
error "Not all protocols are supported",
|
|
|
|
expected = conf.protocols, supported = nodeProtocols
|
2022-10-11 03:58:44 +00:00
|
|
|
return 1
|
|
|
|
elif conStatus == CannotConnect:
|
|
|
|
error "Could not connect", peerId = peer.peerId
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
|
|
when isMainModule:
|
2023-02-06 11:53:05 +00:00
|
|
|
let rng = crypto.newRng()
|
|
|
|
let status = waitFor main(rng)
|
2022-10-11 03:58:44 +00:00
|
|
|
if status == 0:
|
|
|
|
info "The node is reachable and supports all specified protocols"
|
|
|
|
else:
|
|
|
|
error "The node has some problems (see logs)"
|
|
|
|
quit status
|