mirror of
https://github.com/status-im/status-go.git
synced 2025-02-10 13:56:39 +00:00
a6285cc827
* fix(community): stop re-joining comm when receiving a sync community msg Fixes an issue with chats being reset. Since joining a community resaves the chats with the synced default value, it resets the sate of the chats, losing the unread messages, the muted state and more. The solution is to block the re-joining of the community. In the case of the sync, we catch that error and just continue on. * fix(import): fix HandleImport not saving the chat Doesn't change much, but it could have caused issues in the future, so since we might have modified the chat, we make sure to save them Also adds a test * fix tests
443 lines
12 KiB
Go
443 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
stdlog "log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/status-im/status-go/account/generator"
|
|
"github.com/status-im/status-go/api"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/multiaccounts"
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
"github.com/status-im/status-go/multiaccounts/settings"
|
|
"github.com/status-im/status-go/sqlite"
|
|
|
|
"github.com/status-im/status-go/logutils"
|
|
"github.com/status-im/status-go/params"
|
|
"github.com/status-im/status-go/protocol"
|
|
"github.com/status-im/status-go/protocol/common"
|
|
"github.com/status-im/status-go/protocol/identity/alias"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
wakuextn "github.com/status-im/status-go/services/wakuext"
|
|
)
|
|
|
|
const (
|
|
serverClientName = "Statusd"
|
|
)
|
|
|
|
var (
|
|
configFiles configFlags
|
|
logLevel = flag.String("log", "", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
|
|
logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors")
|
|
ipcEnabled = flag.Bool("ipc", false, "Enable IPC RPC endpoint")
|
|
ipcFile = flag.String("ipcfile", "", "Set IPC file path")
|
|
seedPhrase = flag.String("seed-phrase", "", "Seed phrase")
|
|
version = flag.Bool("version", false, "Print version and dump configuration")
|
|
communityID = flag.String("community-id", "", "The id of the community")
|
|
chatID = flag.String("chat-id", "", "The id of the chat")
|
|
|
|
dataDir = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data")
|
|
networkID = flag.Int(
|
|
"network-id",
|
|
params.GoerliNetworkID,
|
|
fmt.Sprintf(
|
|
"A network ID: %d (Mainnet), %d (Goerli)",
|
|
params.MainNetworkID, params.GoerliNetworkID,
|
|
),
|
|
)
|
|
listenAddr = flag.String("addr", "", "address to bind listener to")
|
|
)
|
|
|
|
// All general log messages in this package should be routed through this logger.
|
|
var logger = log.New("package", "status-go/cmd/statusd")
|
|
|
|
func init() {
|
|
flag.Var(&configFiles, "c", "JSON configuration file(s). Multiple configuration files can be specified, and will be merged in occurrence order")
|
|
}
|
|
|
|
// nolint:gocyclo
|
|
func main() {
|
|
colors := terminal.IsTerminal(int(os.Stdin.Fd()))
|
|
if err := logutils.OverrideRootLog(true, "ERROR", logutils.FileOptions{}, colors); err != nil {
|
|
stdlog.Fatalf("Error initializing logger: %v", err)
|
|
}
|
|
|
|
flag.Usage = printUsage
|
|
flag.Parse()
|
|
if flag.NArg() > 0 {
|
|
printUsage()
|
|
logger.Error("Extra args in command line: %v", flag.Args())
|
|
os.Exit(1)
|
|
}
|
|
|
|
opts := []params.Option{}
|
|
|
|
config, err := params.NewNodeConfigWithDefaultsAndFiles(
|
|
*dataDir,
|
|
uint64(*networkID),
|
|
opts,
|
|
configFiles,
|
|
)
|
|
if err != nil {
|
|
printUsage()
|
|
logger.Error(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Use listenAddr if and only if explicitly provided in the arguments.
|
|
// The default value is set in params.NewNodeConfigWithDefaultsAndFiles().
|
|
if *listenAddr != "" {
|
|
config.ListenAddr = *listenAddr
|
|
}
|
|
|
|
// enable IPC RPC
|
|
if *ipcEnabled {
|
|
config.IPCEnabled = true
|
|
config.IPCFile = *ipcFile
|
|
}
|
|
|
|
// set up logging options
|
|
setupLogging(config)
|
|
|
|
// We want statusd to be distinct from StatusIM client.
|
|
config.Name = serverClientName
|
|
|
|
if *version {
|
|
printVersion(config)
|
|
return
|
|
}
|
|
|
|
backend := api.NewGethStatusBackend()
|
|
err = ImportAccount(*seedPhrase, backend)
|
|
if err != nil {
|
|
logger.Error("failed import account", "err", err)
|
|
return
|
|
}
|
|
|
|
wakuextservice := backend.StatusNode().WakuExtService()
|
|
if wakuextservice == nil {
|
|
logger.Error("wakuext not available")
|
|
return
|
|
}
|
|
|
|
wakuext := wakuextn.NewPublicAPI(wakuextservice)
|
|
|
|
// This will start the push notification server as well as
|
|
// the config is set to Enabled
|
|
_, err = wakuext.StartMessenger()
|
|
if err != nil {
|
|
logger.Error("failed to start messenger", "error", err)
|
|
return
|
|
}
|
|
|
|
messenger := wakuextservice.Messenger()
|
|
|
|
community, err := messenger.RequestCommunityInfoFromMailserver(*communityID, true)
|
|
if err != nil {
|
|
|
|
logger.Error("community error", "error", err)
|
|
return
|
|
|
|
}
|
|
chat := community.Chats()[*chatID]
|
|
if chat == nil {
|
|
logger.Warn("Chat not found")
|
|
return
|
|
}
|
|
logger.Info("GOT community", "comm", chat)
|
|
|
|
response, err := messenger.JoinCommunity(context.Background(), community.ID(), false)
|
|
if err != nil {
|
|
logger.Error("failed to join community", "err", err)
|
|
}
|
|
|
|
var targetChat *protocol.Chat
|
|
|
|
for _, c := range response.Chats() {
|
|
if strings.Contains(c.ID, *chatID) {
|
|
targetChat = c
|
|
}
|
|
}
|
|
|
|
if targetChat == nil {
|
|
logger.Warn("chat not found")
|
|
return
|
|
}
|
|
|
|
id := uuid.New().String()
|
|
|
|
ticker := time.NewTicker(2 * time.Second)
|
|
count := 0
|
|
|
|
for { // nolint: gosimple
|
|
select {
|
|
case <-ticker.C:
|
|
count++
|
|
timestamp := time.Now().Format(time.RFC3339)
|
|
logger.Info("Publishing", "id", id, "count", count, "time", timestamp)
|
|
inputMessage := &common.Message{}
|
|
|
|
inputMessage.Text = fmt.Sprintf("%d\n%s\n%s", count, timestamp, id)
|
|
inputMessage.LocalChatID = targetChat.ID
|
|
inputMessage.ChatId = targetChat.ID
|
|
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
|
|
|
|
_, err := messenger.SendChatMessage(context.Background(), inputMessage)
|
|
if err != nil {
|
|
logger.Error("failed to send a message", "err", err)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func getDefaultDataDir() string {
|
|
if home := os.Getenv("HOME"); home != "" {
|
|
return filepath.Join(home, ".statusd")
|
|
}
|
|
return "./statusd-data"
|
|
}
|
|
|
|
func setupLogging(config *params.NodeConfig) {
|
|
if *logLevel != "" {
|
|
config.LogLevel = *logLevel
|
|
}
|
|
|
|
logSettings := logutils.LogSettings{
|
|
Enabled: config.LogEnabled,
|
|
MobileSystem: config.LogMobileSystem,
|
|
Level: config.LogLevel,
|
|
File: config.LogFile,
|
|
MaxSize: config.LogMaxSize,
|
|
MaxBackups: config.LogMaxBackups,
|
|
CompressRotated: config.LogCompressRotated,
|
|
}
|
|
colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd()))
|
|
if err := logutils.OverrideRootLogWithConfig(logSettings, colors); err != nil {
|
|
stdlog.Fatalf("Error initializing logger: %v", err)
|
|
}
|
|
}
|
|
|
|
// printVersion prints verbose output about version and config.
|
|
func printVersion(config *params.NodeConfig) {
|
|
fmt.Println(strings.Title(config.Name))
|
|
fmt.Println("Version:", config.Version)
|
|
fmt.Println("Network ID:", config.NetworkID)
|
|
fmt.Println("Go Version:", runtime.Version())
|
|
fmt.Println("OS:", runtime.GOOS)
|
|
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
|
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
|
|
|
|
fmt.Println("Loaded Config: ", config)
|
|
}
|
|
|
|
func printUsage() {
|
|
usage := `
|
|
Usage: ping-community [options]
|
|
Example:
|
|
ping-community --seed-phrase "your seed phrase" --community-id "community-id" --chat-id "chat-id"
|
|
Options:
|
|
`
|
|
fmt.Fprint(os.Stderr, usage)
|
|
flag.PrintDefaults()
|
|
}
|
|
|
|
const pathWalletRoot = "m/44'/60'/0'/0"
|
|
const pathEIP1581 = "m/43'/60'/1581'"
|
|
const pathDefaultChat = pathEIP1581 + "/0'/0"
|
|
const pathDefaultWallet = pathWalletRoot + "/0"
|
|
|
|
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
|
|
|
|
func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) {
|
|
chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
|
|
|
|
settings := &settings.Settings{}
|
|
settings.KeyUID = generatedAccountInfo.KeyUID
|
|
settings.Address = types.HexToAddress(generatedAccountInfo.Address)
|
|
settings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
|
|
|
|
// Set chat key & name
|
|
name, err := alias.GenerateFromPublicKeyString(chatKeyString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
settings.Name = name
|
|
settings.PublicKey = chatKeyString
|
|
|
|
settings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address)
|
|
settings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address)
|
|
settings.Mnemonic = mnemonic
|
|
|
|
signingPhrase, err := buildSigningPhrase()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
settings.SigningPhrase = signingPhrase
|
|
|
|
settings.SendPushNotifications = true
|
|
settings.InstallationID = uuid.New().String()
|
|
settings.UseMailservers = true
|
|
|
|
settings.PreviewPrivacy = true
|
|
settings.Currency = "usd"
|
|
settings.ProfilePicturesVisibility = 1
|
|
settings.LinkPreviewRequestEnabled = true
|
|
|
|
visibleTokens := make(map[string][]string)
|
|
visibleTokens["mainnet"] = []string{"SNT"}
|
|
visibleTokensJSON, err := json.Marshal(visibleTokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
|
|
settings.WalletVisibleTokens = &visibleTokenJSONRaw
|
|
|
|
// TODO: fix this
|
|
networks := make([]map[string]string, 0)
|
|
networksJSON, err := json.Marshal(networks)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
networkRawMessage := json.RawMessage(networksJSON)
|
|
settings.Networks = &networkRawMessage
|
|
settings.CurrentNetwork = "mainnet_rpc"
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
func defaultNodeConfig(installationID string) (*params.NodeConfig, error) {
|
|
// Set mainnet
|
|
nodeConfig := ¶ms.NodeConfig{}
|
|
nodeConfig.NetworkID = 1
|
|
nodeConfig.LogLevel = "ERROR"
|
|
nodeConfig.DataDir = "/ethereum/mainnet_rpc"
|
|
nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
|
|
Enabled: true,
|
|
URL: "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938",
|
|
}
|
|
|
|
nodeConfig.Name = "StatusIM"
|
|
clusterConfig, err := params.LoadClusterConfigFromFleet("eth.prod")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nodeConfig.ClusterConfig = *clusterConfig
|
|
|
|
nodeConfig.WalletConfig = params.WalletConfig{Enabled: true}
|
|
nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
|
|
nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true}
|
|
nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true}
|
|
nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true}
|
|
nodeConfig.EnableNTPSync = true
|
|
nodeConfig.WakuConfig = params.WakuConfig{
|
|
Enabled: true,
|
|
LightClient: true,
|
|
MinimumPoW: 0.000001,
|
|
}
|
|
|
|
nodeConfig.ShhextConfig = params.ShhextConfig{
|
|
BackupDisabledDataDir: "",
|
|
InstallationID: installationID,
|
|
MaxMessageDeliveryAttempts: 6,
|
|
MailServerConfirmations: true,
|
|
VerifyTransactionURL: "",
|
|
VerifyENSURL: "",
|
|
VerifyENSContractAddress: "",
|
|
VerifyTransactionChainID: 1,
|
|
DataSyncEnabled: true,
|
|
PFSEnabled: true,
|
|
}
|
|
|
|
// TODO: check topics
|
|
|
|
return nodeConfig, nil
|
|
}
|
|
|
|
func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
|
|
backend.UpdateRootDataDir("./tmp")
|
|
manager := backend.AccountManager()
|
|
if err := manager.InitKeystore("./tmp"); err != nil {
|
|
return err
|
|
}
|
|
err := backend.OpenAccounts()
|
|
if err != nil {
|
|
logger.Error("failed open accounts", err)
|
|
return err
|
|
}
|
|
generator := manager.AccountsGenerator()
|
|
generatedAccountInfo, err := generator.ImportMnemonic(seedPhrase, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
derivedAddresses, err := generator.DeriveAddresses(generatedAccountInfo.ID, paths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, "", paths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
account := multiaccounts.Account{
|
|
KeyUID: generatedAccountInfo.KeyUID,
|
|
KDFIterations: sqlite.ReducedKDFIterationsNumber,
|
|
}
|
|
settings, err := defaultSettings(generatedAccountInfo, derivedAddresses, &seedPhrase)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nodeConfig, err := defaultNodeConfig(settings.InstallationID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
walletDerivedAccount := derivedAddresses[pathDefaultWallet]
|
|
walletAccount := &accounts.Account{
|
|
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
|
|
KeyUID: generatedAccountInfo.KeyUID,
|
|
Address: types.HexToAddress(walletDerivedAccount.Address),
|
|
Color: "",
|
|
Wallet: true,
|
|
Path: pathDefaultWallet,
|
|
Name: "Ethereum account",
|
|
}
|
|
|
|
chatDerivedAccount := derivedAddresses[pathDefaultChat]
|
|
chatAccount := &accounts.Account{
|
|
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
|
|
KeyUID: generatedAccountInfo.KeyUID,
|
|
Address: types.HexToAddress(chatDerivedAccount.Address),
|
|
Name: settings.Name,
|
|
Chat: true,
|
|
Path: pathDefaultChat,
|
|
}
|
|
|
|
fmt.Println(nodeConfig)
|
|
accounts := []*accounts.Account{walletAccount, chatAccount}
|
|
err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts)
|
|
if err != nil {
|
|
logger.Error("start node", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|