github.com/status-im/status-go@v1.1.0/cmd/ping-community/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	stdlog "log"
     9  	"os"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/google/uuid"
    16  	"golang.org/x/crypto/ssh/terminal"
    17  
    18  	"github.com/ethereum/go-ethereum/log"
    19  
    20  	"github.com/status-im/status-go/account/generator"
    21  	"github.com/status-im/status-go/api"
    22  	"github.com/status-im/status-go/common/dbsetup"
    23  	"github.com/status-im/status-go/eth-node/types"
    24  	"github.com/status-im/status-go/multiaccounts"
    25  	"github.com/status-im/status-go/multiaccounts/accounts"
    26  	"github.com/status-im/status-go/multiaccounts/settings"
    27  
    28  	"github.com/status-im/status-go/logutils"
    29  	"github.com/status-im/status-go/params"
    30  	"github.com/status-im/status-go/protocol"
    31  	"github.com/status-im/status-go/protocol/common"
    32  	"github.com/status-im/status-go/protocol/common/shard"
    33  	"github.com/status-im/status-go/protocol/identity/alias"
    34  	"github.com/status-im/status-go/protocol/protobuf"
    35  	wakuextn "github.com/status-im/status-go/services/wakuext"
    36  )
    37  
    38  const (
    39  	serverClientName = "Statusd"
    40  )
    41  
    42  var (
    43  	configFiles      configFlags
    44  	logLevel         = flag.String("log", "", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
    45  	logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors")
    46  	ipcEnabled       = flag.Bool("ipc", false, "Enable IPC RPC endpoint")
    47  	ipcFile          = flag.String("ipcfile", "", "Set IPC file path")
    48  	seedPhrase       = flag.String("seed-phrase", "", "Seed phrase")
    49  	version          = flag.Bool("version", false, "Print version and dump configuration")
    50  	communityID      = flag.String("community-id", "", "The id of the community")
    51  	shardCluster     = flag.Int("shard-cluster", shard.MainStatusShardCluster, "The shard cluster in which the of the community is published")
    52  	shardIndex       = flag.Int("shard-index", shard.DefaultShardIndex, "The shard index in which the community is published")
    53  	chatID           = flag.String("chat-id", "", "The id of the chat")
    54  
    55  	dataDir   = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data")
    56  	networkID = flag.Int(
    57  		"network-id",
    58  		params.GoerliNetworkID,
    59  		fmt.Sprintf(
    60  			"A network ID: %d (Mainnet), %d (Goerli)",
    61  			params.MainNetworkID, params.GoerliNetworkID,
    62  		),
    63  	)
    64  	listenAddr = flag.String("addr", "", "address to bind listener to")
    65  )
    66  
    67  // All general log messages in this package should be routed through this logger.
    68  var logger = log.New("package", "status-go/cmd/statusd")
    69  
    70  func init() {
    71  	flag.Var(&configFiles, "c", "JSON configuration file(s). Multiple configuration files can be specified, and will be merged in occurrence order")
    72  }
    73  
    74  // nolint:gocyclo
    75  func main() {
    76  	colors := terminal.IsTerminal(int(os.Stdin.Fd()))
    77  	if err := logutils.OverrideRootLog(true, "ERROR", logutils.FileOptions{}, colors); err != nil {
    78  		stdlog.Fatalf("Error initializing logger: %v", err)
    79  	}
    80  
    81  	flag.Usage = printUsage
    82  	flag.Parse()
    83  	if flag.NArg() > 0 {
    84  		printUsage()
    85  		logger.Error("Extra args in command line: %v", flag.Args())
    86  		os.Exit(1)
    87  	}
    88  
    89  	opts := []params.Option{}
    90  
    91  	config, err := params.NewNodeConfigWithDefaultsAndFiles(
    92  		*dataDir,
    93  		uint64(*networkID),
    94  		opts,
    95  		configFiles,
    96  	)
    97  	if err != nil {
    98  		printUsage()
    99  		logger.Error(err.Error())
   100  		os.Exit(1)
   101  	}
   102  
   103  	// Use listenAddr if and only if explicitly provided in the arguments.
   104  	// The default value is set in params.NewNodeConfigWithDefaultsAndFiles().
   105  	if *listenAddr != "" {
   106  		config.ListenAddr = *listenAddr
   107  	}
   108  
   109  	// enable IPC RPC
   110  	if *ipcEnabled {
   111  		config.IPCEnabled = true
   112  		config.IPCFile = *ipcFile
   113  	}
   114  
   115  	// set up logging options
   116  	setupLogging(config)
   117  
   118  	// We want statusd to be distinct from StatusIM client.
   119  	config.Name = serverClientName
   120  
   121  	if *version {
   122  		printVersion(config)
   123  		return
   124  	}
   125  
   126  	backend := api.NewGethStatusBackend()
   127  	err = ImportAccount(*seedPhrase, backend)
   128  	if err != nil {
   129  		logger.Error("failed import account", "err", err)
   130  		return
   131  	}
   132  
   133  	wakuextservice := backend.StatusNode().WakuExtService()
   134  	if wakuextservice == nil {
   135  		logger.Error("wakuext not available")
   136  		return
   137  	}
   138  
   139  	wakuext := wakuextn.NewPublicAPI(wakuextservice)
   140  
   141  	// This will start the push notification server as well as
   142  	// the config is set to Enabled
   143  	_, err = wakuext.StartMessenger()
   144  	if err != nil {
   145  		logger.Error("failed to start messenger", "error", err)
   146  		return
   147  	}
   148  
   149  	messenger := wakuextservice.Messenger()
   150  
   151  	var s *shard.Shard = nil
   152  	if shardCluster != nil && shardIndex != nil {
   153  		s = &shard.Shard{
   154  			Cluster: uint16(*shardCluster),
   155  			Index:   uint16(*shardIndex),
   156  		}
   157  	}
   158  
   159  	community, err := messenger.FetchCommunity(&protocol.FetchCommunityRequest{
   160  		CommunityKey:    *communityID,
   161  		Shard:           s,
   162  		TryDatabase:     true,
   163  		WaitForResponse: true,
   164  	})
   165  	if err != nil {
   166  
   167  		logger.Error("community error", "error", err)
   168  		return
   169  
   170  	}
   171  	chat := community.Chats()[*chatID]
   172  	if chat == nil {
   173  		logger.Warn("Chat not found")
   174  		return
   175  	}
   176  	logger.Info("GOT community", "comm", chat)
   177  
   178  	response, err := messenger.JoinCommunity(context.Background(), community.ID(), false)
   179  	if err != nil {
   180  		logger.Error("failed to join community", "err", err)
   181  	}
   182  
   183  	var targetChat *protocol.Chat
   184  
   185  	for _, c := range response.Chats() {
   186  		if strings.Contains(c.ID, *chatID) {
   187  			targetChat = c
   188  		}
   189  	}
   190  
   191  	if targetChat == nil {
   192  		logger.Warn("chat not found")
   193  		return
   194  	}
   195  
   196  	id := uuid.New().String()
   197  
   198  	ticker := time.NewTicker(2 * time.Second)
   199  	count := 0
   200  
   201  	for { // nolint: gosimple
   202  		select {
   203  		case <-ticker.C:
   204  			count++
   205  			timestamp := time.Now().UTC().Format(time.RFC3339)
   206  			logger.Info("Publishing", "id", id, "count", count, "time", timestamp)
   207  			inputMessage := common.NewMessage()
   208  
   209  			inputMessage.Text = fmt.Sprintf("%d\n%s\n%s", count, timestamp, id)
   210  			inputMessage.LocalChatID = targetChat.ID
   211  			inputMessage.ChatId = targetChat.ID
   212  			inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
   213  
   214  			_, err := messenger.SendChatMessage(context.Background(), inputMessage)
   215  			if err != nil {
   216  				logger.Error("failed to send a message", "err", err)
   217  			}
   218  
   219  		}
   220  	}
   221  
   222  }
   223  
   224  func getDefaultDataDir() string {
   225  	if home := os.Getenv("HOME"); home != "" {
   226  		return filepath.Join(home, ".statusd")
   227  	}
   228  	return "./statusd-data"
   229  }
   230  
   231  func setupLogging(config *params.NodeConfig) {
   232  	if *logLevel != "" {
   233  		config.LogLevel = *logLevel
   234  	}
   235  
   236  	logSettings := logutils.LogSettings{
   237  		Enabled:         config.LogEnabled,
   238  		MobileSystem:    config.LogMobileSystem,
   239  		Level:           config.LogLevel,
   240  		File:            config.LogFile,
   241  		MaxSize:         config.LogMaxSize,
   242  		MaxBackups:      config.LogMaxBackups,
   243  		CompressRotated: config.LogCompressRotated,
   244  	}
   245  	colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd()))
   246  	if err := logutils.OverrideRootLogWithConfig(logSettings, colors); err != nil {
   247  		stdlog.Fatalf("Error initializing logger: %v", err)
   248  	}
   249  }
   250  
   251  // printVersion prints verbose output about version and config.
   252  func printVersion(config *params.NodeConfig) {
   253  	fmt.Println(strings.Title(config.Name))
   254  	fmt.Println("Version:", config.Version)
   255  	fmt.Println("Network ID:", config.NetworkID)
   256  	fmt.Println("Go Version:", runtime.Version())
   257  	fmt.Println("OS:", runtime.GOOS)
   258  	fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
   259  	fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
   260  
   261  	fmt.Println("Loaded Config: ", config)
   262  }
   263  
   264  func printUsage() {
   265  	usage := `
   266  Usage: ping-community [options]
   267  Example:
   268    ping-community --seed-phrase "your seed phrase" --community-id "community-id" --chat-id "chat-id"
   269  Options:
   270  `
   271  	fmt.Fprint(os.Stderr, usage)
   272  	flag.PrintDefaults()
   273  }
   274  
   275  const pathWalletRoot = "m/44'/60'/0'/0"
   276  const pathEIP1581 = "m/43'/60'/1581'"
   277  const pathDefaultChat = pathEIP1581 + "/0'/0"
   278  const pathDefaultWallet = pathWalletRoot + "/0"
   279  
   280  var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
   281  
   282  func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) {
   283  	chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
   284  
   285  	defaultSettings := &settings.Settings{}
   286  	defaultSettings.KeyUID = generatedAccountInfo.KeyUID
   287  	defaultSettings.Address = types.HexToAddress(generatedAccountInfo.Address)
   288  	defaultSettings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
   289  
   290  	// Set chat key & name
   291  	name, err := alias.GenerateFromPublicKeyString(chatKeyString)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  	defaultSettings.Name = name
   296  	defaultSettings.PublicKey = chatKeyString
   297  
   298  	defaultSettings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address)
   299  	defaultSettings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address)
   300  	defaultSettings.Mnemonic = mnemonic
   301  
   302  	signingPhrase, err := buildSigningPhrase()
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	defaultSettings.SigningPhrase = signingPhrase
   307  
   308  	defaultSettings.SendPushNotifications = true
   309  	defaultSettings.InstallationID = uuid.New().String()
   310  	defaultSettings.UseMailservers = true
   311  
   312  	defaultSettings.PreviewPrivacy = true
   313  	defaultSettings.PeerSyncingEnabled = false
   314  	defaultSettings.Currency = "usd"
   315  	defaultSettings.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone
   316  	defaultSettings.LinkPreviewRequestEnabled = true
   317  
   318  	defaultSettings.TestNetworksEnabled = false
   319  
   320  	visibleTokens := make(map[string][]string)
   321  	visibleTokens["mainnet"] = []string{"SNT"}
   322  	visibleTokensJSON, err := json.Marshal(visibleTokens)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  	visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
   327  	defaultSettings.WalletVisibleTokens = &visibleTokenJSONRaw
   328  
   329  	// TODO: fix this
   330  	networks := make([]map[string]string, 0)
   331  	networksJSON, err := json.Marshal(networks)
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	networkRawMessage := json.RawMessage(networksJSON)
   336  	defaultSettings.Networks = &networkRawMessage
   337  	defaultSettings.CurrentNetwork = "mainnet_rpc"
   338  
   339  	return defaultSettings, nil
   340  }
   341  
   342  func defaultNodeConfig(installationID string) (*params.NodeConfig, error) {
   343  	// Set mainnet
   344  	nodeConfig := &params.NodeConfig{}
   345  	nodeConfig.NetworkID = 1
   346  	nodeConfig.LogLevel = "ERROR"
   347  	nodeConfig.DataDir = api.DefaultDataDir
   348  	nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
   349  		Enabled: true,
   350  		URL:     "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938",
   351  	}
   352  
   353  	nodeConfig.Name = "StatusIM"
   354  	clusterConfig, err := params.LoadClusterConfigFromFleet("eth.prod")
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	nodeConfig.ClusterConfig = *clusterConfig
   359  
   360  	nodeConfig.WalletConfig = params.WalletConfig{Enabled: true}
   361  	nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
   362  	nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true}
   363  	nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true}
   364  	nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true}
   365  	nodeConfig.WakuConfig = params.WakuConfig{
   366  		Enabled:     true,
   367  		LightClient: true,
   368  		MinimumPoW:  0.000001,
   369  	}
   370  
   371  	nodeConfig.ShhextConfig = params.ShhextConfig{
   372  		InstallationID:             installationID,
   373  		MaxMessageDeliveryAttempts: api.DefaultMaxMessageDeliveryAttempts,
   374  		MailServerConfirmations:    true,
   375  		VerifyTransactionURL:       "",
   376  		VerifyENSURL:               "",
   377  		VerifyENSContractAddress:   "",
   378  		VerifyTransactionChainID:   api.DefaultVerifyTransactionChainID,
   379  		DataSyncEnabled:            true,
   380  		PFSEnabled:                 true,
   381  	}
   382  
   383  	// TODO: check topics
   384  
   385  	return nodeConfig, nil
   386  }
   387  
   388  func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
   389  	backend.UpdateRootDataDir("./tmp")
   390  	manager := backend.AccountManager()
   391  	if err := manager.InitKeystore("./tmp"); err != nil {
   392  		return err
   393  	}
   394  	err := backend.OpenAccounts()
   395  	if err != nil {
   396  		logger.Error("failed open accounts", err)
   397  		return err
   398  	}
   399  	generator := manager.AccountsGenerator()
   400  	generatedAccountInfo, err := generator.ImportMnemonic(seedPhrase, "")
   401  	if err != nil {
   402  		return err
   403  	}
   404  
   405  	derivedAddresses, err := generator.DeriveAddresses(generatedAccountInfo.ID, paths)
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	_, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, "", paths)
   411  	if err != nil {
   412  		return err
   413  	}
   414  
   415  	account := multiaccounts.Account{
   416  		KeyUID:        generatedAccountInfo.KeyUID,
   417  		KDFIterations: dbsetup.ReducedKDFIterationsNumber,
   418  	}
   419  	settings, err := defaultSettings(generatedAccountInfo, derivedAddresses, &seedPhrase)
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	nodeConfig, err := defaultNodeConfig(settings.InstallationID)
   425  	if err != nil {
   426  		return err
   427  	}
   428  
   429  	walletDerivedAccount := derivedAddresses[pathDefaultWallet]
   430  	walletAccount := &accounts.Account{
   431  		PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
   432  		KeyUID:    generatedAccountInfo.KeyUID,
   433  		Address:   types.HexToAddress(walletDerivedAccount.Address),
   434  		ColorID:   "",
   435  		Wallet:    true,
   436  		Path:      pathDefaultWallet,
   437  		Name:      "Ethereum account",
   438  	}
   439  
   440  	chatDerivedAccount := derivedAddresses[pathDefaultChat]
   441  	chatAccount := &accounts.Account{
   442  		PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
   443  		KeyUID:    generatedAccountInfo.KeyUID,
   444  		Address:   types.HexToAddress(chatDerivedAccount.Address),
   445  		Name:      settings.Name,
   446  		Chat:      true,
   447  		Path:      pathDefaultChat,
   448  	}
   449  
   450  	fmt.Println(nodeConfig)
   451  	accounts := []*accounts.Account{walletAccount, chatAccount}
   452  	err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
   453  	if err != nil {
   454  		logger.Error("start node", err)
   455  		return err
   456  	}
   457  
   458  	return nil
   459  }