2021-01-07 10:10:38 +00:00
## chat2 is an example of usage of Waku v2. For suggested usage options, please
## see dingpu tutorial in docs folder.
2020-10-01 11:38:32 +00:00
when not ( compileOption ( " threads " ) ) :
{. fatal : " Please, compile this program with the --threads:on option! " . }
2022-11-04 09:52:27 +00:00
when ( NimMajor , NimMinor ) < ( 1 , 4 ) :
{. push raises : [ Defect ] . }
else :
{. push raises : [ ] . }
2021-07-16 15:13:36 +00:00
2023-02-28 13:38:30 +00:00
import std / [ strformat , strutils , times , json , options , random ]
2020-10-01 11:38:32 +00:00
import confutils , chronicles , chronos , stew / shims / net as stewNet ,
2022-11-04 09:52:08 +00:00
eth / keys , bearssl , stew / [ byteutils , results ] ,
2023-04-26 17:25:18 +00:00
nimcrypto / pbkdf2 ,
metrics ,
metrics / chronos_httpserver
2020-10-01 11:38:32 +00:00
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
2021-03-09 07:23:53 +00:00
protobuf / minprotobuf , # message serialisation/deserialisation from and to protobufs
2020-10-01 11:38:32 +00:00
protocols / secure / secio , # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS
2022-11-04 09:52:08 +00:00
nameresolving / dnsresolver ] # define DNS resolution
2022-11-22 17:29:43 +00:00
import
2023-04-19 11:29:23 +00:00
.. / .. / waku / v2 / waku_core ,
2023-04-18 13:22:10 +00:00
.. / .. / waku / v2 / waku_lightpush ,
.. / .. / waku / v2 / waku_lightpush / rpc ,
.. / .. / waku / v2 / waku_filter ,
.. / .. / waku / v2 / waku_store ,
.. / .. / waku / v2 / waku_dnsdisc ,
2023-04-05 09:58:59 +00:00
.. / .. / waku / v2 / waku_node ,
2023-02-07 09:45:25 +00:00
.. / .. / waku / v2 / node / waku_metrics ,
2023-02-06 09:03:30 +00:00
.. / .. / waku / v2 / node / peer_manager ,
2023-02-07 09:45:25 +00:00
.. / .. / waku / v2 / utils / compat ,
2022-10-12 19:41:25 +00:00
.. / .. / waku / common / utils / nat ,
. / config_chat2
2020-10-01 11:38:32 +00:00
2022-11-09 18:45:04 +00:00
when defined ( rln ) :
2022-02-04 23:58:27 +00:00
import
libp2p / protocols / pubsub / rpc / messages ,
2023-02-07 09:45:25 +00:00
libp2p / protocols / pubsub / pubsub
import
2023-04-18 13:22:10 +00:00
.. / .. / waku / v2 / waku_rln_relay
2022-02-04 23:58:27 +00:00
2020-10-01 11:38:32 +00:00
const Help = """
2021-05-26 13:48:09 +00:00
Commands : / [ ? | help | connect | nick | exit ]
2020-10-01 11:38:32 +00:00
help : Prints this help
connect : dials a remote peer
2021-03-04 07:19:21 +00:00
nick : change nickname for current chat session
2021-04-15 08:18:14 +00:00
exit : exits chat session
2020-10-01 11:38:32 +00:00
"""
2020-11-03 20:20:40 +00:00
const
PayloadV1 * {. booldefine . } = false
2020-10-01 11:38:32 +00:00
# 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
2021-03-03 08:40:19 +00:00
nick : string # nickname for this chat session
2021-03-08 07:45:10 +00:00
prompt : bool # chat prompt is showing
2021-05-26 13:48:09 +00:00
contentTopic : string # default content topic for chat messages
symkey : SymKey # SymKey used for v1 payload encryption (if enabled)
2020-10-01 11:38:32 +00:00
type
PrivateKey * = crypto . PrivateKey
2023-04-19 11:29:23 +00:00
Topic * = waku_core . PubsubTopic
2020-10-01 11:38:32 +00:00
2021-03-09 07:23:53 +00:00
#####################
## chat2 protobufs ##
#####################
2021-07-16 15:13:36 +00:00
type
SelectResult * [ T ] = Result [ T , string ]
Chat2Message * = object
timestamp * : int64
nick * : string
payload * : seq [ byte ]
2021-03-09 07:23:53 +00:00
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 toString * ( 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 )
#####################
2020-11-03 20:20:40 +00:00
# Similarly as Status public chats now.
proc generateSymKey ( contentTopic : ContentTopic ) : SymKey =
2023-02-22 14:17:12 +00:00
var ctx : HMAC [ pbkdf2 . sha256 ]
2020-11-03 20:20:40 +00:00
var symKey : SymKey
if pbkdf2 ( ctx , contentTopic . toBytes ( ) , " " , 65356 , symKey ) ! = sizeof ( SymKey ) :
raise ( ref Defect ) ( msg : " Should not occur as array is properly sized " )
symKey
2020-10-22 11:12:00 +00:00
proc connectToNodes ( c : Chat , nodes : seq [ string ] ) {. async . } =
2020-10-14 10:34:29 +00:00
echo " Connecting to nodes "
2020-10-22 11:12:00 +00:00
await c . node . connectToNodes ( nodes )
c . connected = true
2020-10-14 10:34:29 +00:00
2021-03-08 07:45:10 +00:00
proc showChatPrompt ( c : Chat ) =
if not c . prompt :
2021-07-14 17:58:46 +00:00
try :
stdout . write ( " >> " )
stdout . flushFile ( )
c . prompt = true
except IOError :
discard
2022-02-16 22:52:21 +00:00
proc getChatLine ( c : Chat , msg : WakuMessage ) : Result [ string , string ] =
when PayloadV1 :
# Use Waku v1 payload encoding/encryption
let
keyInfo = KeyInfo ( kind : Symmetric , symKey : c . symKey )
decodedPayload = decodePayload ( decoded . get ( ) , keyInfo )
if decodedPayload . isOK ( ) :
let
pb = Chat2Message . init ( decodedPayload . get ( ) . payload )
chatLine = if pb . isOk : pb [ ] . toString ( )
else : string . fromBytes ( decodedPayload . get ( ) . payload )
return ok ( chatLine )
else :
debug " Invalid encoded WakuMessage payload " ,
error = decodedPayload . error
return err ( " Invalid encoded WakuMessage payload " )
else :
# No payload encoding/encryption from Waku
let
pb = Chat2Message . init ( msg . payload )
chatLine = if pb . isOk : pb [ ] . toString ( )
else : string . fromBytes ( msg . payload )
return ok ( chatline )
2021-07-16 15:13:36 +00:00
proc printReceivedMessage ( c : Chat , msg : WakuMessage ) =
2021-06-04 10:02:47 +00:00
when PayloadV1 :
# Use Waku v1 payload encoding/encryption
let
keyInfo = KeyInfo ( kind : Symmetric , symKey : c . symKey )
decodedPayload = decodePayload ( decoded . get ( ) , keyInfo )
if decodedPayload . isOK ( ) :
let
pb = Chat2Message . init ( decodedPayload . get ( ) . payload )
chatLine = if pb . isOk : pb [ ] . toString ( )
else : string . fromBytes ( decodedPayload . get ( ) . payload )
echo & " {chatLine} "
c . prompt = false
showChatPrompt ( c )
2022-11-09 08:55:47 +00:00
trace " Printing message " , topic = DefaultPubsubTopic , chatLine ,
2021-06-04 10:02:47 +00:00
contentTopic = msg . contentTopic
else :
debug " Invalid encoded WakuMessage payload " ,
error = decodedPayload . error
else :
# No payload encoding/encryption from Waku
let
pb = Chat2Message . init ( msg . payload )
chatLine = if pb . isOk : pb [ ] . toString ( )
else : string . fromBytes ( msg . payload )
2021-07-14 17:58:46 +00:00
try :
echo & " {chatLine} "
except ValueError :
# Formatting fail. Print chat line in any case.
echo chatLine
2022-11-22 17:29:43 +00:00
2021-06-04 10:02:47 +00:00
c . prompt = false
showChatPrompt ( c )
2022-11-09 08:55:47 +00:00
trace " Printing message " , topic = DefaultPubsubTopic , chatLine ,
2021-06-04 10:02:47 +00:00
contentTopic = msg . contentTopic
2021-03-03 08:40:19 +00:00
proc readNick ( transp : StreamTransport ) : Future [ string ] {. async . } =
# Chat prompt
stdout . write ( " Choose a nickname >> " )
stdout . flushFile ( )
return await transp . readLine ( )
2023-04-26 17:25:18 +00:00
proc startMetricsServer ( serverIp : ValidIpAddress , serverPort : Port ) : Result [ MetricsHttpServerRef , string ] =
info " Starting metrics HTTP server " , serverIp = $ serverIp , serverPort = $ serverPort
let metricsServerRes = MetricsHttpServerRef . new ( $ serverIp , serverPort )
if metricsServerRes . isErr ( ) :
return err ( " metrics HTTP server start failed: " & $ metricsServerRes . error )
let server = metricsServerRes . value
try :
waitFor server . start ( )
except CatchableError :
return err ( " metrics HTTP server start failed: " & getCurrentExceptionMsg ( ) )
info " Metrics HTTP server started " , serverIp = $ serverIp , serverPort = $ serverPort
ok ( metricsServerRes . value )
2020-10-01 11:38:32 +00:00
proc publish ( c : Chat , line : string ) =
2021-03-09 07:23:53 +00:00
# First create a Chat2Message protobuf with this line of text
2022-02-04 23:58:27 +00:00
let time = getTime ( ) . toUnix ( )
let chat2pb = Chat2Message ( timestamp : time ,
2021-03-09 07:23:53 +00:00
nick : c . nick ,
payload : line . toBytes ( ) ) . encode ( )
2021-06-04 10:02:47 +00:00
## @TODO: error handling on failure
proc handler ( response : PushResponse ) {. gcsafe , closure . } =
trace " lightpush response received " , response = response
2020-11-03 20:20:40 +00:00
when PayloadV1 :
# Use Waku v1 payload encoding/encryption
let
2022-09-07 15:31:27 +00:00
rng = keys . newRng ( )
2021-05-26 13:48:09 +00:00
payload = Payload ( payload : chat2pb . buffer , symKey : some ( c . symKey ) )
2020-11-03 20:20:40 +00:00
version = 1 'u32
2022-09-07 15:31:27 +00:00
encodedPayload = payload . encode ( version , rng [ ] )
2020-11-03 20:20:40 +00:00
if encodedPayload . isOk ( ) :
2022-02-04 23:58:27 +00:00
var message = WakuMessage ( payload : encodedPayload . get ( ) ,
2022-03-18 11:13:32 +00:00
contentTopic : c . contentTopic , version : version , timestamp : getNanosecondTime ( time ) )
2022-11-09 18:45:04 +00:00
when defined ( rln ) :
2022-02-04 23:58:27 +00:00
if not isNil ( c . node . wakuRlnRelay ) :
2022-11-22 17:29:43 +00:00
# for future version when we support more than one rln protected content topic,
2022-02-04 23:58:27 +00:00
# we should check the message content topic as well
let success = c . node . wakuRlnRelay . appendRLNProof ( message , float64 ( time ) )
if not success :
debug " could not append rate limit proof to the message " , success = success
else :
debug " rate limit proof is appended to the message " , success = success
2022-11-22 17:29:43 +00:00
let decodeRes = RateLimitProof . init ( message . proof )
if decodeRes . isErr ( ) :
error " could not decode RLN proof "
let proof = decodeRes . get ( )
# TODO move it to log after dogfooding
let msgEpoch = fromEpoch ( proof . epoch )
if fromEpoch ( c . node . wakuRlnRelay . lastEpoch ) = = fromEpoch ( proof . epoch ) :
2022-08-24 22:47:06 +00:00
echo " --rln epoch: " , msgEpoch , " ⚠️ message rate violation! you are spamming the network! "
else :
echo " --rln epoch: " , msgEpoch
# update the last epoch
2022-11-22 17:29:43 +00:00
c . node . wakuRlnRelay . lastEpoch = proof . epoch
2021-06-04 10:02:47 +00:00
if not c . node . wakuLightPush . isNil ( ) :
# Attempt lightpush
2022-11-09 08:55:47 +00:00
asyncSpawn c . node . lightpushPublish ( DefaultPubsubTopic , message )
2021-06-04 10:02:47 +00:00
else :
2022-11-09 08:55:47 +00:00
asyncSpawn c . node . publish ( DefaultPubsubTopic , message , handler )
2020-11-03 20:20:40 +00:00
else :
warn " Payload encoding failed " , error = encodedPayload . error
else :
# No payload encoding/encryption from Waku
2022-02-04 23:58:27 +00:00
var message = WakuMessage ( payload : chat2pb . buffer ,
2022-03-18 11:13:32 +00:00
contentTopic : c . contentTopic , version : 0 , timestamp : getNanosecondTime ( time ) )
2022-11-09 18:45:04 +00:00
when defined ( rln ) :
2022-11-22 17:29:43 +00:00
if not isNil ( c . node . wakuRlnRelay ) :
# for future version when we support more than one rln protected content topic,
2022-02-04 23:58:27 +00:00
# we should check the message content topic as well
let success = c . node . wakuRlnRelay . appendRLNProof ( message , float64 ( time ) )
if not success :
debug " could not append rate limit proof to the message " , success = success
else :
debug " rate limit proof is appended to the message " , success = success
2022-11-22 17:29:43 +00:00
let decodeRes = RateLimitProof . init ( message . proof )
if decodeRes . isErr ( ) :
error " could not decode the RLN proof "
let proof = decodeRes . get ( )
# TODO move it to log after dogfooding
let msgEpoch = fromEpoch ( proof . epoch )
2022-08-24 22:47:06 +00:00
if fromEpoch ( c . node . wakuRlnRelay . lastEpoch ) = = msgEpoch :
echo " --rln epoch: " , msgEpoch , " ⚠️ message rate violation! you are spamming the network! "
else :
echo " --rln epoch: " , msgEpoch
# update the last epoch
2022-11-22 17:29:43 +00:00
c . node . wakuRlnRelay . lastEpoch = proof . epoch
2022-02-04 23:58:27 +00:00
2021-06-04 10:02:47 +00:00
if not c . node . wakuLightPush . isNil ( ) :
# Attempt lightpush
2022-11-09 08:55:47 +00:00
asyncSpawn c . node . lightpushPublish ( DefaultPubsubTopic , message )
2021-06-04 10:02:47 +00:00
else :
2022-11-09 08:55:47 +00:00
asyncSpawn c . node . publish ( DefaultPubsubTopic , message )
2020-10-01 11:38:32 +00:00
# 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 . millis )
# 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"
2021-03-03 08:40:19 +00:00
# Chat prompt
2021-03-08 07:45:10 +00:00
showChatPrompt ( c )
2021-03-03 08:40:19 +00:00
2020-10-01 11:38:32 +00:00
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 :
2020-10-22 11:12:00 +00:00
await c . connectToNodes ( @ [ address ] )
2022-11-22 17:29:43 +00:00
2021-03-03 08:40:19 +00:00
elif line . startsWith ( " /nick " ) :
# Set a new nickname
c . nick = await readNick ( c . transp )
echo " You are now known as " & c . nick
2020-10-01 11:38:32 +00:00
2021-04-15 08:18:14 +00:00
elif line . startsWith ( " /exit " ) :
2021-06-04 10:02:47 +00:00
if not c . node . wakuFilter . isNil ( ) :
echo " unsubscribing from content filters... "
2022-11-22 17:29:43 +00:00
2022-11-09 08:55:47 +00:00
await c . node . unsubscribe ( pubsubTopic = DefaultPubsubTopic , contentTopics = c . contentTopic )
2022-11-22 17:29:43 +00:00
2021-06-04 10:02:47 +00:00
echo " quitting... "
await c . node . stop ( )
2021-04-15 08:18:14 +00:00
2021-06-04 10:02:47 +00:00
quit ( QuitSuccess )
2020-10-01 11:38:32 +00:00
else :
# XXX connected state problematic
if c . started :
2021-03-09 07:23:53 +00:00
c . publish ( line )
2020-10-01 11:38:32 +00:00
# TODO Connect to peer logic?
else :
try :
if line . startsWith ( " / " ) and " p2p " in line :
2020-10-22 11:12:00 +00:00
await c . connectToNodes ( @ [ line ] )
2020-10-01 11:38:32 +00:00
except :
echo & " unable to dial remote peer {line} "
echo getCurrentExceptionMsg ( )
proc readWriteLoop ( c : Chat ) {. async . } =
2021-06-04 10:02:47 +00:00
asyncSpawn c . writeAndPrint ( ) # execute the async function but does not block
asyncSpawn c . readAndPrint ( )
2020-10-01 11:38:32 +00:00
2021-07-16 15:13:36 +00:00
proc readInput ( wfd : AsyncFD ) {. thread , raises : [ Defect , CatchableError ] . } =
2020-10-01 11:38:32 +00:00
## 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 " )
2021-07-16 15:13:36 +00:00
{. pop . } # @TODO confutils.nim(775, 17) Error: can raise an unlisted exception: ref IOError
2023-02-06 11:53:05 +00:00
proc processInput ( rfd : AsyncFD , rng : ref HmacDrbgContext ) {. async . } =
2020-10-01 11:38:32 +00:00
let
2022-10-18 23:03:43 +00:00
transp = fromPipe ( rfd )
2021-05-26 13:48:09 +00:00
conf = Chat2Conf . load ( )
2023-02-06 11:53:05 +00:00
nodekey = if conf . nodekey . isSome ( ) : conf . nodekey . get ( )
else : PrivateKey . random ( Secp256k1 , rng [ ] ) . tryGet ( )
2022-11-22 17:29:43 +00:00
2022-10-18 23:03:43 +00:00
# set log level
if conf . logLevel ! = LogLevel . NONE :
setLogLevel ( conf . logLevel )
2023-05-17 16:32:53 +00:00
let natRes = setupNat ( conf . nat , clientId ,
2020-10-01 11:38:32 +00:00
Port ( uint16 ( conf . tcpPort ) + conf . portsShift ) ,
Port ( uint16 ( conf . udpPort ) + conf . portsShift ) )
2023-04-05 12:27:11 +00:00
2023-05-17 16:32:53 +00:00
if natRes . isErr ( ) :
raise newException ( ValueError , " setupNat error " & natRes . error )
let ( extIp , extTcpPort , extUdpPort ) = natRes . get ( )
2023-04-05 12:27:11 +00:00
let node = block :
var builder = WakuNodeBuilder . init ( )
builder . withNodeKey ( nodeKey )
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 ( )
2020-10-01 11:38:32 +00:00
await node . start ( )
2020-10-27 10:40:29 +00:00
2022-10-12 02:18:11 +00:00
if conf . rlnRelayEthAccountPrivateKey = = " " and conf . rlnRelayCredPath = = " " :
raise newException ( ConfigurationError ,
2023-01-16 08:29:45 +00:00
" Either rln-relay-eth-account-private-key or rln-relay-cred-path MUST be passed " )
2022-10-12 02:18:11 +00:00
2022-09-07 15:31:27 +00:00
if conf . relay :
await node . mountRelay ( conf . topics . split ( " " ) )
2022-11-22 17:29:43 +00:00
2022-09-07 15:31:27 +00:00
await node . mountLibp2pPing ( )
2022-11-22 17:29:43 +00:00
2021-03-03 08:40:19 +00:00
let nick = await readNick ( transp )
echo " Welcome, " & nick & " ! "
2020-10-01 11:38:32 +00:00
2021-05-26 13:48:09 +00:00
var chat = Chat ( node : node ,
transp : transp ,
subscribed : true ,
connected : false ,
started : true ,
2022-11-22 17:29:43 +00:00
nick : nick ,
2021-05-26 13:48:09 +00:00
prompt : false ,
contentTopic : conf . contentTopic ,
symKey : generateSymKey ( conf . contentTopic ) )
2022-02-22 12:36:38 +00:00
2020-10-14 10:34:29 +00:00
if conf . staticnodes . len > 0 :
2022-02-28 12:28:53 +00:00
echo " Connecting to static peers... "
2020-10-22 11:12:00 +00:00
await connectToNodes ( chat , conf . staticnodes )
2022-11-22 17:29:43 +00:00
2022-02-22 12:36:38 +00:00
var dnsDiscoveryUrl = none ( string )
if conf . fleet ! = Fleet . none :
# Use DNS discovery to connect to selected fleet
2022-02-28 12:28:53 +00:00
echo " Connecting to " & $ conf . fleet & " fleet using DNS discovery... "
2022-11-22 17:29:43 +00:00
2022-02-22 12:36:38 +00:00
if conf . fleet = = Fleet . test :
2022-09-22 12:51:17 +00:00
dnsDiscoveryUrl = some ( " enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im " )
2022-02-22 12:36:38 +00:00
else :
# Connect to prod by default
2022-09-22 12:51:17 +00:00
dnsDiscoveryUrl = some ( " enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@prod.waku.nodes.status.im " )
2022-02-22 12:36:38 +00:00
2021-08-25 11:57:35 +00:00
elif conf . dnsDiscovery and conf . dnsDiscoveryUrl ! = " " :
2022-02-22 12:36:38 +00:00
# No pre-selected fleet. Discover nodes via DNS using user config
debug " Discovering nodes using Waku DNS discovery " , url = conf . dnsDiscoveryUrl
dnsDiscoveryUrl = some ( conf . dnsDiscoveryUrl )
2021-08-25 11:57:35 +00:00
2022-02-22 12:36:38 +00:00
var discoveredNodes : seq [ RemotePeerInfo ]
2021-08-25 11:57:35 +00:00
2022-02-22 12:36:38 +00:00
if dnsDiscoveryUrl . isSome :
var nameServers : seq [ TransportAddress ]
for ip in conf . dnsDiscoveryNameServers :
nameServers . add ( initTAddress ( ip , Port ( 53 ) ) ) # Assume all servers use port 53
2021-08-25 11:57:35 +00:00
2022-02-22 12:36:38 +00:00
let dnsResolver = DnsResolver . new ( nameServers )
2021-03-03 08:40:19 +00:00
2022-02-22 12:36:38 +00:00
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
2022-11-22 17:29:43 +00:00
2022-02-22 12:36:38 +00:00
var wakuDnsDiscovery = WakuDnsDiscovery . init ( dnsDiscoveryUrl . get ( ) ,
resolver )
if wakuDnsDiscovery . isOk :
let discoveredPeers = 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 )
2021-07-16 15:13:36 +00:00
else :
2022-02-22 12:36:38 +00:00
warn " Failed to init Waku DNS discovery "
2020-10-14 10:34:29 +00:00
2022-01-10 15:07:35 +00:00
let peerInfo = node . switch . peerInfo
2020-10-01 11:38:32 +00:00
let listenStr = $ peerInfo . addrs [ 0 ] & " /p2p/ " & $ peerInfo . peerId
echo & " Listening on \n {listenStr} "
2021-03-04 07:19:21 +00:00
if ( conf . storenode ! = " " ) or ( conf . store = = true ) :
2022-09-20 09:39:52 +00:00
await node . mountStore ( )
2020-10-20 02:36:27 +00:00
2022-02-22 12:36:38 +00:00
var storenode : Option [ RemotePeerInfo ]
2021-03-04 07:19:21 +00:00
if conf . storenode ! = " " :
2023-04-12 09:29:11 +00:00
let peerInfo = parsePeerInfo ( conf . storenode )
if peerInfo . isOk ( ) :
storenode = some ( peerInfo . value )
else :
error " Incorrect conf.storenode " , error = peerInfo . error
2022-02-22 12:36:38 +00:00
elif discoveredNodes . len > 0 :
echo " Store enabled, but no store nodes configured. Choosing one at random from discovered peers "
2022-09-06 11:27:55 +00:00
storenode = some ( discoveredNodes [ rand ( 0 .. len ( discoveredNodes ) - 1 ) ] )
2022-11-22 17:29:43 +00:00
2021-05-26 13:48:09 +00:00
if storenode . isSome ( ) :
# We have a viable storenode. Let's query it for historical messages.
2022-02-22 12:36:38 +00:00
echo " Connecting to storenode: " & $ ( storenode . get ( ) )
2020-10-15 11:56:53 +00:00
2022-10-28 18:11:28 +00:00
node . mountStoreClient ( )
2023-02-27 17:24:31 +00:00
node . peerManager . addServicePeer ( storenode . get ( ) , WakuStoreCodec )
2021-05-26 13:48:09 +00:00
proc storeHandler ( response : HistoryResponse ) {. gcsafe . } =
for msg in response . messages :
let
pb = Chat2Message . init ( msg . payload )
chatLine = if pb . isOk : pb [ ] . toString ( )
else : string . fromBytes ( msg . payload )
echo & " {chatLine} "
info " Hit store handler "
2020-10-15 11:56:53 +00:00
2022-11-09 17:50:18 +00:00
let queryRes = await node . query ( HistoryQuery ( contentTopics : @ [ chat . contentTopic ] ) )
2022-09-21 16:27:40 +00:00
if queryRes . isOk ( ) :
storeHandler ( queryRes . value )
2022-11-22 17:29:43 +00:00
2021-06-04 10:02:47 +00:00
# NOTE Must be mounted after relay
if conf . lightpushnode ! = " " :
2023-04-12 09:29:11 +00:00
let peerInfo = parsePeerInfo ( conf . lightpushnode )
if peerInfo . isOk ( ) :
await mountLightPush ( node )
node . mountLightPushClient ( )
node . peerManager . addServicePeer ( peerInfo . value , WakuLightpushCodec )
else :
error " LightPush not mounted. Couldn ' t parse conf.lightpushnode " ,
error = peerInfo . error
2020-10-15 11:56:53 +00:00
2020-10-27 10:40:29 +00:00
if conf . filternode ! = " " :
2023-04-12 09:29:11 +00:00
let peerInfo = parsePeerInfo ( conf . filternode )
if peerInfo . isOk ( ) :
await node . mountFilter ( )
await node . mountFilterClient ( )
node . peerManager . addServicePeer ( peerInfo . value , WakuFilterCodec )
2020-10-27 10:40:29 +00:00
2023-04-12 09:29:11 +00:00
proc filterHandler ( pubsubTopic : PubsubTopic , msg : WakuMessage ) {. gcsafe . } =
trace " Hit filter handler " , contentTopic = msg . contentTopic
chat . printReceivedMessage ( msg )
2021-06-04 10:02:47 +00:00
2023-04-12 09:29:11 +00:00
await node . subscribe ( pubsubTopic = DefaultPubsubTopic , contentTopics = chat . contentTopic , filterHandler )
2020-10-27 10:40:29 +00:00
2023-04-12 09:29:11 +00:00
else :
error " Filter not mounted. Couldn ' t parse conf.filternode " ,
error = peerInfo . error
2020-10-27 10:40:29 +00:00
2021-06-04 10:02:47 +00:00
# Subscribe to a topic, if relay is mounted
if conf . relay :
proc handler ( topic : Topic , data : seq [ byte ] ) {. async , gcsafe . } =
trace " Hit subscribe handler " , topic
2020-11-03 20:20:40 +00:00
2022-11-07 15:24:16 +00:00
let decoded = WakuMessage . decode ( data )
2022-11-22 17:29:43 +00:00
2021-06-04 10:02:47 +00:00
if decoded . isOk ( ) :
2022-03-18 11:13:32 +00:00
if decoded . get ( ) . contentTopic = = chat . contentTopic :
chat . printReceivedMessage ( decoded . get ( ) )
2020-11-03 20:20:40 +00:00
else :
2021-06-04 10:02:47 +00:00
trace " Invalid encoded WakuMessage " , error = decoded . error
2020-10-01 11:38:32 +00:00
2022-11-09 08:55:47 +00:00
let topic = DefaultPubsubTopic
2021-06-04 10:02:47 +00:00
node . subscribe ( topic , handler )
2020-10-01 11:38:32 +00:00
2022-11-22 17:29:43 +00:00
when defined ( rln ) :
2022-02-04 23:58:27 +00:00
if conf . rlnRelay :
info " WakuRLNRelay is enabled "
2022-02-16 22:52:21 +00:00
proc spamHandler ( wakuMessage : WakuMessage ) {. gcsafe , closure . } =
debug " spam handler is called "
let chatLineResult = chat . getChatLine ( wakuMessage )
if chatLineResult . isOk ( ) :
echo " A spam message is found and discarded : " , chatLineResult . value
else :
echo " A spam message is found and discarded "
2022-03-18 22:51:26 +00:00
chat . prompt = false
showChatPrompt ( chat )
2022-08-24 22:47:06 +00:00
proc registrationHandler ( txHash : string ) {. gcsafe , closure . } =
echo " You are registered to the rln membership contract, find details of your registration transaction in https://goerli.etherscan.io/tx/0x " , txHash
2022-11-22 17:29:43 +00:00
2022-08-30 17:59:02 +00:00
echo " rln-relay preparation is in progress... "
2022-11-22 17:29:43 +00:00
2022-11-08 11:53:47 +00:00
let rlnConf = WakuRlnConfig (
rlnRelayDynamic : conf . rlnRelayDynamic ,
rlnRelayPubsubTopic : conf . rlnRelayPubsubTopic ,
rlnRelayContentTopic : conf . rlnRelayContentTopic ,
2023-02-28 13:38:30 +00:00
rlnRelayMembershipIndex : some ( conf . rlnRelayMembershipIndex ) ,
2022-11-08 11:53:47 +00:00
rlnRelayEthContractAddress : conf . rlnRelayEthContractAddress ,
rlnRelayEthClientAddress : conf . rlnRelayEthClientAddress ,
rlnRelayEthAccountPrivateKey : conf . rlnRelayEthAccountPrivateKey ,
rlnRelayEthAccountAddress : conf . rlnRelayEthAccountAddress ,
rlnRelayCredPath : conf . rlnRelayCredPath ,
rlnRelayCredentialsPassword : conf . rlnRelayCredentialsPassword
)
2023-01-16 08:29:45 +00:00
await node . mountRlnRelay ( rlnConf ,
2022-12-13 09:26:24 +00:00
spamHandler = some ( spamHandler ) ,
registrationHandler = some ( registrationHandler ) )
2023-02-28 13:38:30 +00:00
let membershipIndex = node . wakuRlnRelay . groupManager . membershipIndex . get ( )
let identityCredential = node . wakuRlnRelay . groupManager . idCredentials . get ( )
echo " your membership index is: " , membershipIndex
echo " your rln identity trapdoor is: " , identityCredential . idTrapdoor . inHex ( )
echo " your rln identity nullifier is: " , identityCredential . idNullifier . inHex ( )
echo " your rln identity secret hash is: " , identityCredential . idSecretHash . inHex ( )
echo " your rln identity commitment key is: " , identityCredential . idCommitment . inHex ( )
2023-01-16 08:29:45 +00:00
else :
info " WakuRLNRelay is disabled "
if conf . rlnRelay :
echo " WakuRLNRelay is disabled, please enable it by compiling with the RLN/EXPERIMENTAL flag "
2022-10-21 08:33:36 +00:00
if conf . metricsLogging :
startMetricsLog ( )
if conf . metricsServer :
2023-04-26 17:25:18 +00:00
let metricsServer = startMetricsServer (
conf . metricsServerAddress ,
Port ( conf . metricsServerPort + conf . portsShift )
)
2022-11-22 17:29:43 +00:00
2022-10-21 08:33:36 +00:00
2020-10-01 11:38:32 +00:00
await chat . readWriteLoop ( )
2021-04-15 08:18:14 +00:00
2021-06-02 07:53:34 +00:00
if conf . keepAlive :
node . startKeepalive ( )
2020-10-01 11:38:32 +00:00
runForever ( )
2023-02-06 11:53:05 +00:00
proc main ( rng : ref HmacDrbgContext ) {. async . } =
2020-10-01 11:38:32 +00:00
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 )
2022-10-12 02:18:11 +00:00
try :
2023-02-06 11:53:05 +00:00
await processInput ( rfd , rng )
2022-10-12 02:18:11 +00:00
# Handle only ConfigurationError for now
# TODO: Throw other errors from the mounting procedure
except ConfigurationError as e :
raise e
2020-10-01 11:38:32 +00:00
when isMainModule : # isMainModule = true when the module is compiled as the main file
2023-02-06 11:53:05 +00:00
let rng = crypto . newRng ( )
2022-10-12 02:18:11 +00:00
try :
2023-02-06 11:53:05 +00:00
waitFor ( main ( rng ) )
2022-10-12 02:18:11 +00:00
except CatchableError as e :
raise e
2020-10-01 11:38:32 +00:00
## 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
## - Integrate store protocol (fetch messages in beginning)
## - Integrate filter protocol (default/option to be light node, connect to filter node)
## - 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)