mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-02 05:53:11 +00:00
* update rendezvous to work with WakuPeeRecord and use libp2p updated version * split rendezvous client and service implementation * mount rendezvous client by default
661 lines
21 KiB
Nim
661 lines
21 KiB
Nim
## chat2 is an example of usage of Waku v2. For suggested usage options, please
|
|
## see dingpu tutorial in docs folder.
|
|
|
|
when not (compileOption("threads")):
|
|
{.fatal: "Please, compile this program with the --threads:on option!".}
|
|
|
|
{.push raises: [].}
|
|
|
|
import std/[strformat, strutils, times, options, random, sequtils]
|
|
import
|
|
confutils,
|
|
chronicles,
|
|
chronos,
|
|
eth/keys,
|
|
bearssl,
|
|
results,
|
|
stew/[byteutils],
|
|
metrics,
|
|
metrics/chronos_httpserver
|
|
import
|
|
libp2p/[
|
|
switch, # manage transports, a single entry point for dialing and listening
|
|
crypto/crypto, # cryptographic functions
|
|
stream/connection, # create and close stream read / write connections
|
|
multiaddress,
|
|
# encode different addressing schemes. For example, /ip4/7.7.7.7/tcp/6543 means it is using IPv4 protocol and TCP
|
|
peerinfo,
|
|
# manage the information of a peer, such as peer ID and public / private key
|
|
peerid, # Implement how peers interact
|
|
protobuf/minprotobuf, # message serialisation/deserialisation from and to protobufs
|
|
nameresolving/dnsresolver,
|
|
protocols/mix/curve25519,
|
|
] # define DNS resolution
|
|
import
|
|
waku/[
|
|
waku_core,
|
|
waku_lightpush/common,
|
|
waku_lightpush/rpc,
|
|
waku_enr,
|
|
discovery/waku_dnsdisc,
|
|
waku_node,
|
|
node/waku_metrics,
|
|
node/peer_manager,
|
|
factory/builder,
|
|
common/utils/nat,
|
|
waku_store/common,
|
|
waku_filter_v2/client,
|
|
common/logging,
|
|
],
|
|
./config_chat2mix
|
|
|
|
import libp2p/protocols/pubsub/rpc/messages, libp2p/protocols/pubsub/pubsub
|
|
import ../../waku/waku_rln_relay
|
|
|
|
logScope:
|
|
topics = "chat2 mix"
|
|
|
|
const Help =
|
|
"""
|
|
Commands: /[?|help|connect|nick|exit]
|
|
help: Prints this help
|
|
connect: dials a remote peer
|
|
nick: change nickname for current chat session
|
|
exit: exits chat session
|
|
"""
|
|
|
|
# XXX Connected is a bit annoying, because incoming connections don't trigger state change
|
|
# Could poll connection pool or something here, I suppose
|
|
# TODO Ensure connected turns true on incoming connections, or get rid of it
|
|
type Chat = ref object
|
|
node: WakuNode # waku node for publishing, subscribing, etc
|
|
transp: StreamTransport # transport streams between read & write file descriptor
|
|
subscribed: bool # indicates if a node is subscribed or not to a topic
|
|
connected: bool # if the node is connected to another peer
|
|
started: bool # if the node has started
|
|
nick: string # nickname for this chat session
|
|
prompt: bool # chat prompt is showing
|
|
contentTopic: string # default content topic for chat messages
|
|
conf: Chat2Conf # configuration for chat2
|
|
|
|
type
|
|
PrivateKey* = crypto.PrivateKey
|
|
Topic* = waku_core.PubsubTopic
|
|
|
|
#####################
|
|
## chat2 protobufs ##
|
|
#####################
|
|
|
|
type
|
|
SelectResult*[T] = Result[T, string]
|
|
|
|
Chat2Message* = object
|
|
timestamp*: int64
|
|
nick*: string
|
|
payload*: seq[byte]
|
|
|
|
proc getPubsubTopic*(
|
|
conf: Chat2Conf, node: WakuNode, contentTopic: string
|
|
): PubsubTopic =
|
|
let shard = node.wakuAutoSharding.get().getShard(contentTopic).valueOr:
|
|
echo "Could not parse content topic: " & error
|
|
return "" #TODO: fix this.
|
|
return $RelayShard(clusterId: conf.clusterId, shardId: shard.shardId)
|
|
|
|
proc init*(T: type Chat2Message, buffer: seq[byte]): ProtoResult[T] =
|
|
var msg = Chat2Message()
|
|
let pb = initProtoBuffer(buffer)
|
|
|
|
var timestamp: uint64
|
|
discard ?pb.getField(1, timestamp)
|
|
msg.timestamp = int64(timestamp)
|
|
|
|
discard ?pb.getField(2, msg.nick)
|
|
discard ?pb.getField(3, msg.payload)
|
|
|
|
ok(msg)
|
|
|
|
proc encode*(message: Chat2Message): ProtoBuffer =
|
|
var serialised = initProtoBuffer()
|
|
|
|
serialised.write(1, uint64(message.timestamp))
|
|
serialised.write(2, message.nick)
|
|
serialised.write(3, message.payload)
|
|
|
|
return serialised
|
|
|
|
proc `$`*(message: Chat2Message): string =
|
|
# Get message date and timestamp in local time
|
|
let time = message.timestamp.fromUnix().local().format("'<'MMM' 'dd,' 'HH:mm'>'")
|
|
|
|
return time & " " & message.nick & ": " & string.fromBytes(message.payload)
|
|
|
|
#####################
|
|
|
|
proc connectToNodes(c: Chat, nodes: seq[string]) {.async.} =
|
|
echo "Connecting to nodes"
|
|
await c.node.connectToNodes(nodes)
|
|
c.connected = true
|
|
|
|
proc showChatPrompt(c: Chat) =
|
|
if not c.prompt:
|
|
try:
|
|
stdout.write(">> ")
|
|
stdout.flushFile()
|
|
c.prompt = true
|
|
except IOError:
|
|
discard
|
|
|
|
proc getChatLine(payload: seq[byte]): string =
|
|
# No payload encoding/encryption from Waku
|
|
let pb = Chat2Message.init(payload).valueOr:
|
|
return string.fromBytes(payload)
|
|
return $pb
|
|
|
|
proc printReceivedMessage(c: Chat, msg: WakuMessage) =
|
|
let chatLine = getChatLine(msg.payload)
|
|
try:
|
|
echo &"{chatLine}"
|
|
except ValueError:
|
|
# Formatting fail. Print chat line in any case.
|
|
echo chatLine
|
|
|
|
c.prompt = false
|
|
showChatPrompt(c)
|
|
trace "Printing message", chatLine, contentTopic = msg.contentTopic
|
|
|
|
proc readNick(transp: StreamTransport): Future[string] {.async.} =
|
|
# Chat prompt
|
|
stdout.write("Choose a nickname >> ")
|
|
stdout.flushFile()
|
|
return await transp.readLine()
|
|
|
|
proc startMetricsServer(
|
|
serverIp: IpAddress, serverPort: Port
|
|
): Result[MetricsHttpServerRef, string] =
|
|
info "Starting metrics HTTP server", serverIp = $serverIp, serverPort = $serverPort
|
|
|
|
let server = MetricsHttpServerRef.new($serverIp, serverPort).valueOr:
|
|
return err("metrics HTTP server start failed: " & $error)
|
|
|
|
try:
|
|
waitFor server.start()
|
|
except CatchableError:
|
|
return err("metrics HTTP server start failed: " & getCurrentExceptionMsg())
|
|
|
|
info "Metrics HTTP server started", serverIp = $serverIp, serverPort = $serverPort
|
|
ok(server)
|
|
|
|
proc publish(c: Chat, line: string) {.async.} =
|
|
# First create a Chat2Message protobuf with this line of text
|
|
let time = getTime().toUnix()
|
|
let chat2pb =
|
|
Chat2Message(timestamp: time, nick: c.nick, payload: line.toBytes()).encode()
|
|
|
|
## @TODO: error handling on failure
|
|
proc handler(response: LightPushResponse) {.gcsafe, closure.} =
|
|
trace "lightpush response received", response = response
|
|
|
|
var message = WakuMessage(
|
|
payload: chat2pb.buffer,
|
|
contentTopic: c.contentTopic,
|
|
version: 0,
|
|
timestamp: getNanosecondTime(time),
|
|
)
|
|
|
|
try:
|
|
if not c.node.wakuLightpushClient.isNil():
|
|
# Attempt lightpush with mix
|
|
|
|
(
|
|
waitFor c.node.lightpushPublish(
|
|
some(c.conf.getPubsubTopic(c.node, c.contentTopic)),
|
|
message,
|
|
none(RemotePeerInfo),
|
|
true,
|
|
)
|
|
).isOkOr:
|
|
error "failed to publish lightpush message", error = error
|
|
else:
|
|
error "failed to publish message as lightpush client is not initialized"
|
|
except CatchableError:
|
|
error "caught error publishing message: ", error = getCurrentExceptionMsg()
|
|
|
|
# TODO This should read or be subscribe handler subscribe
|
|
proc readAndPrint(c: Chat) {.async.} =
|
|
while true:
|
|
# while p.connected:
|
|
# # TODO: echo &"{p.id} -> "
|
|
#
|
|
# echo cast[string](await p.conn.readLp(1024))
|
|
#echo "readAndPrint subscribe NYI"
|
|
await sleepAsync(100)
|
|
|
|
# TODO Implement
|
|
proc writeAndPrint(c: Chat) {.async.} =
|
|
while true:
|
|
# Connect state not updated on incoming WakuRelay connections
|
|
# if not c.connected:
|
|
# echo "type an address or wait for a connection:"
|
|
# echo "type /[help|?] for help"
|
|
|
|
# Chat prompt
|
|
showChatPrompt(c)
|
|
|
|
let line = await c.transp.readLine()
|
|
if line.startsWith("/help") or line.startsWith("/?") or not c.started:
|
|
echo Help
|
|
continue
|
|
|
|
# if line.startsWith("/disconnect"):
|
|
# echo "Ending current session"
|
|
# if p.connected and p.conn.closed.not:
|
|
# await p.conn.close()
|
|
# p.connected = false
|
|
elif line.startsWith("/connect"):
|
|
# TODO Should be able to connect to multiple peers for Waku chat
|
|
if c.connected:
|
|
echo "already connected to at least one peer"
|
|
continue
|
|
|
|
echo "enter address of remote peer"
|
|
let address = await c.transp.readLine()
|
|
if address.len > 0:
|
|
await c.connectToNodes(@[address])
|
|
elif line.startsWith("/nick"):
|
|
# Set a new nickname
|
|
c.nick = await readNick(c.transp)
|
|
echo "You are now known as " & c.nick
|
|
elif line.startsWith("/exit"):
|
|
echo "quitting..."
|
|
|
|
try:
|
|
await c.node.stop()
|
|
except:
|
|
echo "exception happened when stopping: " & getCurrentExceptionMsg()
|
|
|
|
quit(QuitSuccess)
|
|
else:
|
|
# XXX connected state problematic
|
|
if c.started:
|
|
echo "publishing message: " & line
|
|
await c.publish(line)
|
|
# TODO Connect to peer logic?
|
|
else:
|
|
try:
|
|
if line.startsWith("/") and "p2p" in line:
|
|
await c.connectToNodes(@[line])
|
|
except:
|
|
echo &"unable to dial remote peer {line}"
|
|
echo getCurrentExceptionMsg()
|
|
|
|
proc readWriteLoop(c: Chat) {.async.} =
|
|
asyncSpawn c.writeAndPrint() # execute the async function but does not block
|
|
asyncSpawn c.readAndPrint()
|
|
|
|
proc readInput(wfd: AsyncFD) {.thread, raises: [Defect, CatchableError].} =
|
|
## This procedure performs reading from `stdin` and sends data over
|
|
## pipe to main thread.
|
|
let transp = fromPipe(wfd)
|
|
|
|
while true:
|
|
let line = stdin.readLine()
|
|
discard waitFor transp.write(line & "\r\n")
|
|
|
|
var alreadyUsedServicePeers {.threadvar.}: seq[RemotePeerInfo]
|
|
|
|
proc selectRandomServicePeer*(
|
|
pm: PeerManager, actualPeer: Option[RemotePeerInfo], codec: string
|
|
): Result[RemotePeerInfo, void] =
|
|
if actualPeer.isSome():
|
|
alreadyUsedServicePeers.add(actualPeer.get())
|
|
|
|
let supportivePeers = pm.switch.peerStore.getPeersByProtocol(codec).filterIt(
|
|
it notin alreadyUsedServicePeers
|
|
)
|
|
if supportivePeers.len == 0:
|
|
return err()
|
|
|
|
let rndPeerIndex = rand(0 .. supportivePeers.len - 1)
|
|
return ok(supportivePeers[rndPeerIndex])
|
|
|
|
proc maintainSubscription(
|
|
wakuNode: WakuNode,
|
|
filterPubsubTopic: PubsubTopic,
|
|
filterContentTopic: ContentTopic,
|
|
filterPeer: RemotePeerInfo,
|
|
preventPeerSwitch: bool,
|
|
) {.async.} =
|
|
var actualFilterPeer = filterPeer
|
|
const maxFailedSubscribes = 3
|
|
const maxFailedServiceNodeSwitches = 10
|
|
var noFailedSubscribes = 0
|
|
var noFailedServiceNodeSwitches = 0
|
|
# Use chronos.Duration explicitly to avoid mismatch with std/times.Duration
|
|
let RetryWait = chronos.seconds(2) # Quick retry interval
|
|
let SubscriptionMaintenance = chronos.seconds(30) # Subscription maintenance interval
|
|
while true:
|
|
info "maintaining subscription at", peer = constructMultiaddrStr(actualFilterPeer)
|
|
# First use filter-ping to check if we have an active subscription
|
|
let pingErr = (await wakuNode.wakuFilterClient.ping(actualFilterPeer)).errorOr:
|
|
await sleepAsync(SubscriptionMaintenance)
|
|
info "subscription is live."
|
|
continue
|
|
|
|
# No subscription found. Let's subscribe.
|
|
error "ping failed.", error = pingErr
|
|
trace "no subscription found. Sending subscribe request"
|
|
|
|
let subscribeErr = (
|
|
await wakuNode.filterSubscribe(
|
|
some(filterPubsubTopic), filterContentTopic, actualFilterPeer
|
|
)
|
|
).errorOr:
|
|
await sleepAsync(SubscriptionMaintenance)
|
|
if noFailedSubscribes > 0:
|
|
noFailedSubscribes -= 1
|
|
notice "subscribe request successful."
|
|
continue
|
|
|
|
noFailedSubscribes += 1
|
|
error "Subscribe request failed.",
|
|
error = subscribeErr, peer = actualFilterPeer, failCount = noFailedSubscribes
|
|
|
|
# TODO: disconnet from failed actualFilterPeer
|
|
# asyncSpawn(wakuNode.peerManager.switch.disconnect(p))
|
|
# wakunode.peerManager.peerStore.delete(actualFilterPeer)
|
|
|
|
if noFailedSubscribes < maxFailedSubscribes:
|
|
await sleepAsync(RetryWait) # Wait a bit before retrying
|
|
elif not preventPeerSwitch:
|
|
# try again with new peer without delay
|
|
let actualFilterPeer = selectRandomServicePeer(
|
|
wakuNode.peerManager, some(actualFilterPeer), WakuFilterSubscribeCodec
|
|
).valueOr:
|
|
error "Failed to find new service peer. Exiting."
|
|
noFailedServiceNodeSwitches += 1
|
|
break
|
|
|
|
info "Found new peer for codec",
|
|
codec = filterPubsubTopic, peer = constructMultiaddrStr(actualFilterPeer)
|
|
|
|
noFailedSubscribes = 0
|
|
else:
|
|
await sleepAsync(SubscriptionMaintenance)
|
|
|
|
{.pop.}
|
|
# @TODO confutils.nim(775, 17) Error: can raise an unlisted exception: ref IOError
|
|
proc processInput(rfd: AsyncFD, rng: ref HmacDrbgContext) {.async.} =
|
|
let
|
|
transp = fromPipe(rfd)
|
|
conf = Chat2Conf.load()
|
|
nodekey =
|
|
if conf.nodekey.isSome():
|
|
conf.nodekey.get()
|
|
else:
|
|
PrivateKey.random(Secp256k1, rng[]).tryGet()
|
|
|
|
# set log level
|
|
if conf.logLevel != LogLevel.NONE:
|
|
setLogLevel(conf.logLevel)
|
|
|
|
let (extIp, extTcpPort, extUdpPort) = setupNat(
|
|
conf.nat,
|
|
clientId,
|
|
Port(uint16(conf.tcpPort) + conf.portsShift),
|
|
Port(uint16(conf.udpPort) + conf.portsShift),
|
|
).valueOr:
|
|
raise newException(ValueError, "setupNat error " & error)
|
|
|
|
var enrBuilder = EnrBuilder.init(nodeKey)
|
|
|
|
enrBuilder.withWakuRelaySharding(
|
|
RelayShards(clusterId: conf.clusterId, shardIds: conf.shards)
|
|
).isOkOr:
|
|
error "failed to add sharded topics to ENR", error = error
|
|
quit(QuitFailure)
|
|
|
|
let record = enrBuilder.build().valueOr:
|
|
error "failed to create enr record", error = error
|
|
quit(QuitFailure)
|
|
|
|
let node = block:
|
|
var builder = WakuNodeBuilder.init()
|
|
builder.withNodeKey(nodeKey)
|
|
builder.withRecord(record)
|
|
|
|
builder
|
|
.withNetworkConfigurationDetails(
|
|
conf.listenAddress,
|
|
Port(uint16(conf.tcpPort) + conf.portsShift),
|
|
extIp,
|
|
extTcpPort,
|
|
wsBindPort = Port(uint16(conf.websocketPort) + conf.portsShift),
|
|
wsEnabled = conf.websocketSupport,
|
|
wssEnabled = conf.websocketSecureSupport,
|
|
)
|
|
.tryGet()
|
|
builder.build().tryGet()
|
|
|
|
node.mountAutoSharding(conf.clusterId, conf.numShardsInNetwork).isOkOr:
|
|
error "failed to mount waku sharding: ", error = error
|
|
quit(QuitFailure)
|
|
node.mountMetadata(conf.clusterId, conf.shards).isOkOr:
|
|
error "failed to mount waku metadata protocol: ", err = error
|
|
quit(QuitFailure)
|
|
|
|
let (mixPrivKey, mixPubKey) = generateKeyPair().valueOr:
|
|
error "failed to generate mix key pair", error = error
|
|
return
|
|
|
|
(await node.mountMix(conf.clusterId, mixPrivKey, conf.mixnodes)).isOkOr:
|
|
error "failed to mount waku mix protocol: ", error = $error
|
|
quit(QuitFailure)
|
|
await node.mountRendezvousClient(conf.clusterId)
|
|
|
|
await node.start()
|
|
|
|
node.peerManager.start()
|
|
|
|
await node.mountLibp2pPing()
|
|
await node.mountPeerExchangeClient()
|
|
let pubsubTopic = conf.getPubsubTopic(node, conf.contentTopic)
|
|
echo "pubsub topic is: " & pubsubTopic
|
|
let nick = await readNick(transp)
|
|
echo "Welcome, " & nick & "!"
|
|
|
|
var chat = Chat(
|
|
node: node,
|
|
transp: transp,
|
|
subscribed: true,
|
|
connected: false,
|
|
started: true,
|
|
nick: nick,
|
|
prompt: false,
|
|
contentTopic: conf.contentTopic,
|
|
conf: conf,
|
|
)
|
|
|
|
var dnsDiscoveryUrl = none(string)
|
|
|
|
if conf.fleet != Fleet.none:
|
|
# Use DNS discovery to connect to selected fleet
|
|
echo "Connecting to " & $conf.fleet & " fleet using DNS discovery..."
|
|
|
|
if conf.fleet == Fleet.test:
|
|
dnsDiscoveryUrl = some(
|
|
"enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im"
|
|
)
|
|
else:
|
|
# Connect to sandbox by default
|
|
dnsDiscoveryUrl = some(
|
|
"enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im"
|
|
)
|
|
elif conf.dnsDiscoveryUrl != "":
|
|
# No pre-selected fleet. Discover nodes via DNS using user config
|
|
info "Discovering nodes using Waku DNS discovery", url = conf.dnsDiscoveryUrl
|
|
dnsDiscoveryUrl = some(conf.dnsDiscoveryUrl)
|
|
|
|
var discoveredNodes: seq[RemotePeerInfo]
|
|
|
|
if dnsDiscoveryUrl.isSome:
|
|
var nameServers: seq[TransportAddress]
|
|
for ip in conf.dnsDiscoveryNameServers:
|
|
nameServers.add(initTAddress(ip, Port(53))) # Assume all servers use port 53
|
|
|
|
let dnsResolver = DnsResolver.new(nameServers)
|
|
|
|
proc resolver(domain: string): Future[string] {.async, gcsafe.} =
|
|
trace "resolving", domain = domain
|
|
let resolved = await dnsResolver.resolveTxt(domain)
|
|
return resolved[0] # Use only first answer
|
|
|
|
let wakuDnsDiscovery = WakuDnsDiscovery.init(dnsDiscoveryUrl.get(), resolver)
|
|
if wakuDnsDiscovery.isOk:
|
|
let discoveredPeers = await wakuDnsDiscovery.get().findPeers()
|
|
if discoveredPeers.isOk:
|
|
info "Connecting to discovered peers"
|
|
discoveredNodes = discoveredPeers.get()
|
|
echo "Discovered and connecting to " & $discoveredNodes
|
|
waitFor chat.node.connectToNodes(discoveredNodes)
|
|
else:
|
|
warn "Failed to find peers via DNS discovery", error = discoveredPeers.error
|
|
else:
|
|
warn "Failed to init Waku DNS discovery", error = wakuDnsDiscovery.error
|
|
|
|
let peerInfo = node.switch.peerInfo
|
|
let listenStr = $peerInfo.addrs[0] & "/p2p/" & $peerInfo.peerId
|
|
echo &"Listening on\n {listenStr}"
|
|
|
|
if (conf.storenode != "") or (conf.store == true):
|
|
await node.mountStore()
|
|
|
|
var storenode: Option[RemotePeerInfo]
|
|
|
|
if conf.storenode != "":
|
|
let peerInfo = parsePeerInfo(conf.storenode)
|
|
if peerInfo.isOk():
|
|
storenode = some(peerInfo.value)
|
|
else:
|
|
error "Incorrect conf.storenode", error = peerInfo.error
|
|
elif discoveredNodes.len > 0:
|
|
echo "Store enabled, but no store nodes configured. Choosing one at random from discovered peers"
|
|
storenode = some(discoveredNodes[rand(0 .. len(discoveredNodes) - 1)])
|
|
|
|
if storenode.isSome():
|
|
# We have a viable storenode. Let's query it for historical messages.
|
|
echo "Connecting to storenode: " & $(storenode.get())
|
|
|
|
node.mountStoreClient()
|
|
node.peerManager.addServicePeer(storenode.get(), WakuStoreCodec)
|
|
|
|
proc storeHandler(response: StoreQueryResponse) {.gcsafe.} =
|
|
for msg in response.messages:
|
|
let payload =
|
|
if msg.message.isSome():
|
|
msg.message.get().payload
|
|
else:
|
|
newSeq[byte](0)
|
|
|
|
let chatLine = getChatLine(payload)
|
|
echo &"{chatLine}"
|
|
info "Hit store handler"
|
|
|
|
let queryRes = await node.query(
|
|
StoreQueryRequest(contentTopics: @[chat.contentTopic]), storenode.get()
|
|
)
|
|
if queryRes.isOk():
|
|
storeHandler(queryRes.value)
|
|
|
|
if conf.edgemode: #Mount light protocol clients
|
|
node.mountLightPushClient()
|
|
await node.mountFilterClient()
|
|
let filterHandler = proc(
|
|
pubsubTopic: PubsubTopic, msg: WakuMessage
|
|
): Future[void] {.async, closure.} =
|
|
trace "Hit filter handler", contentTopic = msg.contentTopic
|
|
chat.printReceivedMessage(msg)
|
|
|
|
node.wakuFilterClient.registerPushHandler(filterHandler)
|
|
var servicePeerInfo: RemotePeerInfo
|
|
if conf.serviceNode != "":
|
|
servicePeerInfo = parsePeerInfo(conf.serviceNode).valueOr:
|
|
error "Couldn't parse conf.serviceNode", error = error
|
|
RemotePeerInfo()
|
|
if servicePeerInfo == nil or $servicePeerInfo.peerId == "":
|
|
# Assuming that service node supports all services
|
|
servicePeerInfo = selectRandomServicePeer(
|
|
node.peerManager, none(RemotePeerInfo), WakuLightpushCodec
|
|
).valueOr:
|
|
error "Couldn't find any service peer"
|
|
quit(QuitFailure)
|
|
|
|
node.peerManager.addServicePeer(servicePeerInfo, WakuLightpushCodec)
|
|
node.peerManager.addServicePeer(servicePeerInfo, WakuPeerExchangeCodec)
|
|
|
|
# Start maintaining subscription
|
|
asyncSpawn maintainSubscription(
|
|
node, pubsubTopic, conf.contentTopic, servicePeerInfo, false
|
|
)
|
|
echo "waiting for mix nodes to be discovered..."
|
|
while true:
|
|
if node.getMixNodePoolSize() >= 3:
|
|
break
|
|
discard await node.fetchPeerExchangePeers()
|
|
await sleepAsync(1000)
|
|
|
|
while node.getMixNodePoolSize() < 3:
|
|
info "waiting for mix nodes to be discovered",
|
|
currentpoolSize = node.getMixNodePoolSize()
|
|
await sleepAsync(1000)
|
|
notice "ready to publish with mix node pool size ",
|
|
currentpoolSize = node.getMixNodePoolSize()
|
|
echo "ready to publish messages now"
|
|
|
|
# Once min mixnodes are discovered loop as per default setting
|
|
node.startPeerExchangeLoop()
|
|
|
|
if conf.metricsLogging:
|
|
startMetricsLog()
|
|
|
|
if conf.metricsServer:
|
|
let metricsServer = startMetricsServer(
|
|
conf.metricsServerAddress, Port(conf.metricsServerPort + conf.portsShift)
|
|
)
|
|
|
|
await chat.readWriteLoop()
|
|
|
|
runForever()
|
|
|
|
proc main(rng: ref HmacDrbgContext) {.async.} =
|
|
let (rfd, wfd) = createAsyncPipe()
|
|
if rfd == asyncInvalidPipe or wfd == asyncInvalidPipe:
|
|
raise newException(ValueError, "Could not initialize pipe!")
|
|
|
|
var thread: Thread[AsyncFD]
|
|
thread.createThread(readInput, wfd)
|
|
try:
|
|
await processInput(rfd, rng)
|
|
# Handle only ConfigurationError for now
|
|
# TODO: Throw other errors from the mounting procedure
|
|
except ConfigurationError as e:
|
|
raise e
|
|
|
|
when isMainModule: # isMainModule = true when the module is compiled as the main file
|
|
let rng = crypto.newRng()
|
|
try:
|
|
waitFor(main(rng))
|
|
except CatchableError as e:
|
|
raise e
|
|
|
|
## Dump of things that can be improved:
|
|
##
|
|
## - Incoming dialed peer does not change connected state (not relying on it for now)
|
|
## - Unclear if staticnode argument works (can enter manually)
|
|
## - Don't trigger self / double publish own messages
|
|
## - Test/default to cluster node connection (diff protocol version)
|
|
## - Redirect logs to separate file
|
|
## - Expose basic publish/subscribe etc commands with /syntax
|
|
## - Show part of peerid to know who sent message
|
|
## - Deal with protobuf messages (e.g. other chat protocol, or encrypted)
|