2021-04-04 17:06:17 +00:00
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
2021-09-30 16:02:04 +00:00
"errors"
2021-04-04 17:06:17 +00:00
"flag"
"fmt"
mrand "math/rand"
"net"
"os"
"time"
2022-08-04 21:39:12 +00:00
"github.com/ethereum/go-ethereum/common"
2021-04-04 17:06:17 +00:00
"github.com/ethereum/go-ethereum/crypto"
2021-04-13 19:17:21 +00:00
logging "github.com/ipfs/go-log"
2021-04-04 17:06:17 +00:00
"github.com/libp2p/go-libp2p-core/peer"
2021-09-30 16:02:04 +00:00
"github.com/libp2p/go-libp2p-core/protocol"
2022-07-06 20:29:20 +00:00
"github.com/status-im/go-rln/rln"
2021-11-10 14:28:45 +00:00
wakuprotocol "github.com/status-im/go-waku/waku/v2/protocol"
2022-02-21 17:46:58 +00:00
"github.com/status-im/go-waku/waku/v2/utils"
2021-11-10 14:28:45 +00:00
2021-10-01 10:32:15 +00:00
"github.com/multiformats/go-multiaddr"
2021-11-16 14:22:01 +00:00
"github.com/status-im/go-waku/waku/v2/dnsdisc"
2021-04-04 17:06:17 +00:00
"github.com/status-im/go-waku/waku/v2/node"
2021-09-30 16:02:04 +00:00
"github.com/status-im/go-waku/waku/v2/protocol/filter"
"github.com/status-im/go-waku/waku/v2/protocol/lightpush"
2022-07-06 20:29:20 +00:00
"github.com/status-im/go-waku/waku/v2/protocol/pb"
2021-04-22 00:12:51 +00:00
"github.com/status-im/go-waku/waku/v2/protocol/store"
2021-04-04 17:06:17 +00:00
)
2022-07-06 20:29:20 +00:00
var DefaultContentTopic string = wakuprotocol . NewContentTopic ( "toy-chat" , 2 , "luzhou" , "proto" ) . String ( )
2021-04-06 23:27:54 +00:00
2021-04-04 17:06:17 +00:00
func main ( ) {
mrand . Seed ( time . Now ( ) . UTC ( ) . UnixNano ( ) )
2022-02-21 17:46:58 +00:00
// Display panic level to reduce log noise
lvl , err := logging . LevelFromString ( "panic" )
if err != nil {
panic ( err )
}
logging . SetAllLoggers ( lvl )
// go-waku logger
err = utils . SetLogLevel ( "panic" )
if err != nil {
os . Exit ( 1 )
}
2021-04-04 17:06:17 +00:00
nickFlag := flag . String ( "nick" , "" , "nickname to use in chat. will be generated if empty" )
2021-06-10 13:00:16 +00:00
fleetFlag := flag . String ( "fleet" , "wakuv2.prod" , "Select the fleet to connect to. (wakuv2.prod, wakuv2.test)" )
contentTopicFlag := flag . String ( "contenttopic" , DefaultContentTopic , "content topic to use for the chat" )
2021-06-28 14:14:28 +00:00
nodeKeyFlag := flag . String ( "nodekey" , "" , "private key for this node. will be generated if empty" )
staticNodeFlag := flag . String ( "staticnode" , "" , "connects to a node. will get a random node from fleets.status.im if empty" )
relayFlag := flag . Bool ( "relay" , true , "enable relay protocol" )
storeNodeFlag := flag . String ( "storenode" , "" , "connects to a store node to retrieve messages. will get a random node from fleets.status.im if empty" )
2021-04-04 17:06:17 +00:00
port := flag . Int ( "port" , 0 , "port. Will be random if 0" )
2021-06-10 13:00:16 +00:00
payloadV1Flag := flag . Bool ( "payloadV1" , false , "use Waku v1 payload encoding/encryption. default false" )
2021-06-28 14:14:28 +00:00
filterFlag := flag . Bool ( "filter" , false , "enable filter protocol" )
filterNodeFlag := flag . String ( "filternode" , "" , "multiaddr of peer to to request content filtering of messages" )
lightPushFlag := flag . Bool ( "lightpush" , false , "enable lightpush protocol" )
lightPushNodeFlag := flag . String ( "lightpushnode" , "" , "Multiaddr of peer to to request lightpush of published messages" )
2021-10-06 15:34:39 +00:00
keepAliveFlag := flag . Int64 ( "keep-alive" , 20 , "interval in seconds for pinging peers to keep the connection alive." )
2021-06-28 14:14:28 +00:00
2021-09-30 23:03:19 +00:00
dnsDiscoveryFlag := flag . Bool ( "dns-discovery" , false , "enable dns discovery" )
dnsDiscoveryUrlFlag := flag . String ( "dns-discovery-url" , "" , "URL for DNS node list in format 'enrtree://<key>@<fqdn>'" )
dnsDiscoveryNameServerFlag := flag . String ( "dns-discovery-nameserver" , "" , "DNS name server IP to query (empty to use system default)" )
2022-07-06 20:29:20 +00:00
rlnRelayFlag := flag . Bool ( "rln-relay" , false , "enable spam protection through rln-relay" )
rlnRelayMemIndexFlag := flag . Int ( "rln-relay-membership-index" , 0 , "(experimental) the index of node in the rln-relay group: a value between 0-99 inclusive" )
rlnRelayContentTopicFlag := flag . String ( "rln-relay-content-topic" , "/toy-chat/2/luzhou/proto" , "the content topic for which rln-relay gets enabled" )
rlnRelayPubsubTopicFlag := flag . String ( "rln-relay-pubsub-topic" , "/waku/2/default-waku/proto" , "the pubsub topic for which rln-relay gets enabled" )
2022-08-04 21:39:12 +00:00
rlnRelayDynamicFlag := flag . Bool ( "rln-relay-dynamic" , false , "Enable waku-rln-relay with on-chain dynamic group management" )
rlnRelayIdKeyFlag := flag . String ( "rln-relay-id" , "" , "Rln relay identity secret key as a Hex string" )
rlnRelayIdCommitmentKeyFlag := flag . String ( "rln-relay-id-commitment" , "" , "Rln relay identity commitment key as a Hex string" )
rlnRelayEthAccountPrivKeyFlag := flag . String ( "eth-account-privatekey" , "" , "Account private key for an Ethereum testnet" )
rlnRelayEthClientAddressFlag := flag . String ( "eth-client-address" , "ws://localhost:8545/" , "Ethereum testnet client address" )
rlnRelayEthMemContractAddressFlag := flag . String ( "eth-mem-contract-address" , "" , "Address of membership contract on an Ethereum testnet" )
2022-08-09 00:02:08 +00:00
rlnRelayCredentialsFile := flag . String ( "rln-relay-credentials-file" , "rlnCredentials.txt" , "RLN credentials file" )
2022-08-04 21:39:12 +00:00
2021-04-04 17:06:17 +00:00
flag . Parse ( )
hostAddr , _ := net . ResolveTCPAddr ( "tcp" , fmt . Sprintf ( "0.0.0.0:%d" , * port ) )
2021-06-10 13:00:16 +00:00
if * fleetFlag != "wakuv2.prod" && * fleetFlag != "wakuv2.test" {
fmt . Println ( "Invalid fleet. Valid values are wakuv2.prod and wakuv2.test" )
return
}
2021-04-04 17:06:17 +00:00
// use the nickname from the cli flag, or a default if blank
nodekey := * nodeKeyFlag
if len ( nodekey ) == 0 {
var err error
nodekey , err = randomHex ( 32 )
if err != nil {
fmt . Println ( "Could not generate random key" )
return
}
}
prvKey , err := crypto . HexToECDSA ( nodekey )
ctx := context . Background ( )
2021-06-28 14:14:28 +00:00
opts := [ ] node . WakuNodeOption {
2021-04-19 00:07:21 +00:00
node . WithPrivateKey ( prvKey ) ,
2021-11-17 16:19:42 +00:00
node . WithHostAddress ( hostAddr ) ,
2021-10-31 19:00:38 +00:00
node . WithWakuStore ( false , false ) ,
2021-06-28 14:14:28 +00:00
node . WithKeepAlive ( time . Duration ( * keepAliveFlag ) * time . Second ) ,
}
if * relayFlag {
opts = append ( opts , node . WithWakuRelay ( ) )
}
2022-07-06 20:29:20 +00:00
spamChan := make ( chan * pb . WakuMessage , 100 )
if * rlnRelayFlag {
spamHandler := func ( message * pb . WakuMessage ) error {
spamChan <- message
return nil
}
2022-08-04 21:39:12 +00:00
if * rlnRelayDynamicFlag {
key , err := crypto . ToECDSA ( common . FromHex ( * rlnRelayEthAccountPrivKeyFlag ) )
if err != nil {
panic ( err )
}
2022-08-09 00:02:08 +00:00
idKey , idCommitment , index , err := getMembershipCredentials ( * rlnRelayCredentialsFile , * rlnRelayIdKeyFlag , * rlnRelayIdCommitmentKeyFlag , * rlnRelayMemIndexFlag )
if err != nil {
panic ( err )
2022-08-04 21:39:12 +00:00
}
fmt . Println ( "Setting up dynamic rln" )
opts = append ( opts , node . WithDynamicRLNRelay (
* rlnRelayPubsubTopicFlag ,
* rlnRelayContentTopicFlag ,
2022-08-09 00:02:08 +00:00
index ,
2022-08-04 21:39:12 +00:00
idKey ,
idCommitment ,
spamHandler ,
* rlnRelayEthClientAddressFlag ,
key ,
common . HexToAddress ( * rlnRelayEthMemContractAddressFlag ) ,
) )
} else {
opts = append ( opts , node . WithStaticRLNRelay ( * rlnRelayPubsubTopicFlag , * rlnRelayContentTopicFlag , rln . MembershipIndex ( * rlnRelayMemIndexFlag ) , spamHandler ) )
}
2022-07-06 20:29:20 +00:00
}
2021-06-28 14:14:28 +00:00
if * filterFlag {
2021-10-30 14:29:34 +00:00
opts = append ( opts , node . WithWakuFilter ( false ) )
2021-06-28 14:14:28 +00:00
}
if * lightPushFlag || * lightPushNodeFlag != "" {
* lightPushFlag = true // If a lightpushnode was set and lightpush flag was false
opts = append ( opts , node . WithLightPush ( ) )
}
wakuNode , err := node . New ( ctx , opts ... )
2021-04-04 17:06:17 +00:00
if err != nil {
fmt . Print ( err )
return
}
2021-09-30 16:02:04 +00:00
if * lightPushFlag {
addPeer ( wakuNode , * lightPushNodeFlag , lightpush . LightPushID_v20beta1 )
2021-06-28 14:14:28 +00:00
}
2021-09-30 16:02:04 +00:00
if * filterFlag {
addPeer ( wakuNode , * filterNodeFlag , filter . FilterID_v20beta1 )
2021-06-28 14:14:28 +00:00
}
2021-10-05 02:13:54 +00:00
if err := wakuNode . Start ( ) ; err != nil {
panic ( err )
}
2022-08-09 00:02:08 +00:00
if * rlnRelayFlag && * rlnRelayDynamicFlag {
err := writeRLNMembershipCredentialsToFile ( * rlnRelayCredentialsFile , wakuNode . RLNRelay ( ) . MembershipKeyPair ( ) , wakuNode . RLNRelay ( ) . MembershipIndex ( ) )
if err != nil {
panic ( err )
}
fmt . Printf ( "Wrote credentials in file %s\n" , * rlnRelayCredentialsFile )
}
2021-04-04 17:06:17 +00:00
// use the nickname from the cli flag, or a default if blank
nick := * nickFlag
if len ( nick ) == 0 {
nick = defaultNick ( wakuNode . Host ( ) . ID ( ) )
}
// join the chat
2022-07-06 20:29:20 +00:00
chat , err := NewChat ( ctx , wakuNode , wakuNode . Host ( ) . ID ( ) , * contentTopicFlag , * payloadV1Flag , * lightPushFlag , nick , spamChan )
2021-04-04 17:06:17 +00:00
if err != nil {
panic ( err )
}
2021-04-22 00:12:51 +00:00
ui := NewChatUI ( ctx , chat )
2021-04-04 17:06:17 +00:00
// Connect to a static node or use random node from fleets.status.im
go func ( ) {
time . Sleep ( 200 * time . Millisecond )
staticnode := * staticNodeFlag
2021-04-06 23:27:54 +00:00
storenode := * storeNodeFlag
2021-04-15 02:22:18 +00:00
2021-04-06 23:27:54 +00:00
var fleetData [ ] byte
if len ( staticnode ) == 0 || len ( storenode ) == 0 {
fleetData = getFleetData ( )
}
2021-04-04 17:06:17 +00:00
if len ( staticnode ) == 0 {
2021-06-10 13:00:16 +00:00
ui . displayMessage ( fmt . Sprintf ( "No static peers configured. Choosing one at random from %s fleet..." , * fleetFlag ) )
staticnode = getRandomFleetNode ( fleetData , * fleetFlag )
2021-04-04 17:06:17 +00:00
}
2021-10-01 10:32:15 +00:00
ctx , cancel := context . WithTimeout ( ctx , time . Duration ( 5 ) * time . Second )
defer cancel ( )
err = wakuNode . DialPeer ( ctx , staticnode )
2021-04-04 17:06:17 +00:00
if err != nil {
ui . displayMessage ( "Could not connect to peer: " + err . Error ( ) )
return
} else {
ui . displayMessage ( "Connected to peer: " + staticnode )
}
2021-04-06 23:27:54 +00:00
2021-09-30 23:03:19 +00:00
enableDiscovery := * dnsDiscoveryFlag
dnsDiscoveryUrl := * dnsDiscoveryUrlFlag
dnsDiscoveryNameServer := * dnsDiscoveryNameServerFlag
if enableDiscovery && dnsDiscoveryUrl != "" {
ui . displayMessage ( fmt . Sprintf ( "attempting DNS discovery with %s" , dnsDiscoveryUrl ) )
2022-05-06 19:29:31 +00:00
nodes , err := dnsdisc . RetrieveNodes ( ctx , dnsDiscoveryUrl , dnsdisc . WithNameserver ( dnsDiscoveryNameServer ) )
2021-09-30 23:03:19 +00:00
if err != nil {
ui . displayMessage ( "DNS discovery error: " + err . Error ( ) )
} else {
2022-05-06 19:29:31 +00:00
for _ , n := range nodes {
for _ , m := range n . Addresses {
go func ( ctx context . Context , m multiaddr . Multiaddr ) {
ctx , cancel := context . WithTimeout ( ctx , time . Duration ( 3 ) * time . Second )
defer cancel ( )
err = wakuNode . DialPeerWithMultiAddress ( ctx , m )
if err != nil {
ui . displayMessage ( "error dialing peer: " + err . Error ( ) )
}
} ( ctx , m )
}
2021-09-30 23:03:19 +00:00
}
}
}
2021-04-06 23:27:54 +00:00
if len ( storenode ) == 0 {
2021-06-10 13:00:16 +00:00
ui . displayMessage ( fmt . Sprintf ( "No store node configured. Choosing one at random from %s fleet..." , * fleetFlag ) )
storenode = getRandomFleetNode ( fleetData , * fleetFlag )
2021-04-06 23:27:54 +00:00
}
2022-02-24 13:33:07 +00:00
storeNodeId , err := addPeer ( wakuNode , storenode , store . StoreID_v20beta4 )
2021-04-06 23:27:54 +00:00
if err != nil {
ui . displayMessage ( "Could not connect to storenode: " + err . Error ( ) )
return
} else {
ui . displayMessage ( "Connected to storenode: " + storenode )
}
2021-04-12 18:03:58 +00:00
time . Sleep ( 300 * time . Millisecond )
2021-04-06 23:27:54 +00:00
ui . displayMessage ( "Querying historic messages" )
2021-04-15 02:22:18 +00:00
2021-06-10 13:00:16 +00:00
tCtx , _ := context . WithTimeout ( ctx , 5 * time . Second )
2021-10-31 19:00:38 +00:00
q := store . Query {
ContentTopics : [ ] string { * contentTopicFlag } ,
}
2021-11-01 12:38:03 +00:00
response , err := wakuNode . Store ( ) . Query ( tCtx , q ,
2021-04-15 02:22:18 +00:00
store . WithAutomaticRequestId ( ) ,
2021-04-15 17:57:18 +00:00
store . WithPeer ( * storeNodeId ) ,
2021-04-15 02:22:18 +00:00
store . WithPaging ( true , 0 ) )
2021-04-06 23:27:54 +00:00
if err != nil {
2021-04-12 18:03:58 +00:00
ui . displayMessage ( "Could not query storenode: " + err . Error ( ) )
} else {
chat . displayMessages ( response . Messages )
}
2021-04-04 17:06:17 +00:00
} ( )
2021-04-06 23:27:54 +00:00
//draw the UI
2021-04-04 17:06:17 +00:00
if err = ui . Run ( ) ; err != nil {
printErr ( "error running text UI: %s" , err )
}
2021-06-28 14:14:28 +00:00
wakuNode . Stop ( )
// TODO: filter unsubscribeAll
2021-04-04 17:06:17 +00:00
}
// Generates a random hex string with a length of n
func randomHex ( n int ) ( string , error ) {
bytes := make ( [ ] byte , n )
if _ , err := rand . Read ( bytes ) ; err != nil {
return "" , err
}
return hex . EncodeToString ( bytes ) , nil
}
// printErr is like fmt.Printf, but writes to stderr.
func printErr ( m string , args ... interface { } ) {
fmt . Fprintf ( os . Stderr , m , args ... )
}
// defaultNick generates a nickname based on the $USER environment variable and
// the last 8 chars of a peer ID.
func defaultNick ( p peer . ID ) string {
return fmt . Sprintf ( "%s-%s" , os . Getenv ( "USER" ) , shortID ( p ) )
}
// shortID returns the last 8 chars of a base58-encoded peer id.
func shortID ( p peer . ID ) string {
pretty := p . Pretty ( )
return pretty [ len ( pretty ) - 8 : ]
}
2021-04-06 23:27:54 +00:00
func getFleetData ( ) [ ] byte {
2022-02-21 17:46:58 +00:00
b , err := os . ReadFile ( "fleets.json" )
2021-04-04 17:06:17 +00:00
if err != nil {
2022-02-21 17:46:58 +00:00
panic ( err )
2021-04-04 17:06:17 +00:00
}
2022-02-21 17:46:58 +00:00
return b
2021-04-06 23:27:54 +00:00
}
2021-06-10 13:00:16 +00:00
func getRandomFleetNode ( data [ ] byte , fleetId string ) string {
2021-04-04 17:06:17 +00:00
var result map [ string ] interface { }
2021-04-06 23:27:54 +00:00
json . Unmarshal ( data , & result )
2021-04-04 17:06:17 +00:00
fleets := result [ "fleets" ] . ( map [ string ] interface { } )
2021-06-10 13:00:16 +00:00
fleet := fleets [ fleetId ] . ( map [ string ] interface { } )
waku := fleet [ "waku" ] . ( map [ string ] interface { } )
2021-04-04 17:06:17 +00:00
var wakunodes [ ] string
for v := range waku {
wakunodes = append ( wakunodes , v )
break
}
randKey := wakunodes [ mrand . Intn ( len ( wakunodes ) ) ]
return waku [ randKey ] . ( string )
}
2021-09-30 16:02:04 +00:00
func addPeer ( wakuNode * node . WakuNode , addr string , protocol protocol . ID ) ( * peer . ID , error ) {
if addr == "" {
return nil , errors . New ( "invalid multiaddress" )
}
ma , err := multiaddr . NewMultiaddr ( addr )
if err != nil {
return nil , err
}
2022-04-25 19:31:26 +00:00
return wakuNode . AddPeer ( ma , string ( protocol ) )
2021-09-30 16:02:04 +00:00
}