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"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
mrand "math/rand"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"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"
|
2021-11-10 14:28:45 +00:00
|
|
|
wakuprotocol "github.com/status-im/go-waku/waku/v2/protocol"
|
|
|
|
|
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"
|
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
|
|
|
)
|
|
|
|
|
2021-11-10 14:28:45 +00:00
|
|
|
var DefaultContentTopic string = wakuprotocol.NewContentTopic("toy-chat", 2, "huilong", "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())
|
|
|
|
|
|
|
|
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)")
|
|
|
|
|
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-10-30 14:29:34 +00:00
|
|
|
node.WithHostAddress([]*net.TCPAddr{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())
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
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
|
2021-06-28 14:14:28 +00:00
|
|
|
chat, err := NewChat(ctx, wakuNode, wakuNode.Host().ID(), *contentTopicFlag, *payloadV1Flag, *lightPushFlag, nick)
|
2021-04-04 17:06:17 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2021-04-13 19:17:21 +00:00
|
|
|
// Display panic level to reduce log noise
|
|
|
|
lvl, err := logging.LevelFromString("panic")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
logging.SetAllLoggers(lvl)
|
|
|
|
|
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))
|
2021-11-16 14:22:01 +00:00
|
|
|
multiaddresses, 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 {
|
|
|
|
for _, m := range multiaddresses {
|
2021-10-01 10:32:15 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-09-30 16:02:04 +00:00
|
|
|
storeNodeId, err := addPeer(wakuNode, storenode, store.StoreID_v20beta3)
|
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 {
|
2021-04-04 17:06:17 +00:00
|
|
|
url := "https://fleets.status.im"
|
|
|
|
httpClient := http.Client{
|
|
|
|
Timeout: time.Second * 2,
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
res, getErr := httpClient.Do(req)
|
|
|
|
if getErr != nil {
|
|
|
|
log.Fatal(getErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.Body != nil {
|
|
|
|
defer res.Body.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
body, readErr := ioutil.ReadAll(res.Body)
|
|
|
|
if readErr != nil {
|
|
|
|
log.Fatal(readErr)
|
|
|
|
}
|
|
|
|
|
2021-04-06 23:27:54 +00:00
|
|
|
return body
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
return wakuNode.AddPeer(ma, protocol)
|
|
|
|
}
|