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

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	stdlog "log"
     9  	"math/rand"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/google/uuid"
    17  	"golang.org/x/crypto/ssh/terminal"
    18  
    19  	"github.com/ethereum/go-ethereum/log"
    20  
    21  	"github.com/status-im/status-go/account/generator"
    22  	"github.com/status-im/status-go/api"
    23  	"github.com/status-im/status-go/common/dbsetup"
    24  	"github.com/status-im/status-go/eth-node/crypto"
    25  	"github.com/status-im/status-go/eth-node/types"
    26  	"github.com/status-im/status-go/logutils"
    27  	"github.com/status-im/status-go/multiaccounts"
    28  	"github.com/status-im/status-go/multiaccounts/accounts"
    29  	"github.com/status-im/status-go/multiaccounts/settings"
    30  	"github.com/status-im/status-go/params"
    31  	"github.com/status-im/status-go/protocol"
    32  	"github.com/status-im/status-go/protocol/common"
    33  	"github.com/status-im/status-go/protocol/identity/alias"
    34  	"github.com/status-im/status-go/protocol/protobuf"
    35  	"github.com/status-im/status-go/protocol/requests"
    36  	wakuextn "github.com/status-im/status-go/services/wakuext"
    37  )
    38  
    39  type testTimeSource struct{}
    40  
    41  func (t *testTimeSource) GetCurrentTime() uint64 {
    42  	return uint64(time.Now().Unix()) * 1000
    43  }
    44  
    45  const (
    46  	serverClientName = "Statusd"
    47  )
    48  
    49  var (
    50  	configFiles      configFlags
    51  	logLevel         = flag.String("log", "", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
    52  	logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors")
    53  	ipcEnabled       = flag.Bool("ipc", false, "Enable IPC RPC endpoint")
    54  	ipcFile          = flag.String("ipcfile", "", "Set IPC file path")
    55  	seedPhrase       = flag.String("seed-phrase", "", "Seed phrase")
    56  	version          = flag.Bool("version", false, "Print version and dump configuration")
    57  	nAddedContacts   = flag.Int("added-contacts", 100, "Number of added contacts to create")
    58  	nContacts        = flag.Int("contacts", 100, "Number of contacts to create")
    59  	nPublicChats     = flag.Int("public-chats", 5, "Number of public chats")
    60  	nCommunities     = flag.Int("communities", 5, "Number of communities")
    61  	nMessages        = flag.Int("number-of-messages", 0, "Number of messages for each chat")
    62  	nOneToOneChats   = flag.Int("one-to-one-chats", 5, "Number of one to one chats")
    63  
    64  	dataDir   = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data")
    65  	networkID = flag.Int(
    66  		"network-id",
    67  		params.GoerliNetworkID,
    68  		fmt.Sprintf(
    69  			"A network ID: %d (Mainnet), %d (Goerli)",
    70  			params.MainNetworkID, params.GoerliNetworkID,
    71  		),
    72  	)
    73  	listenAddr = flag.String("addr", "", "address to bind listener to")
    74  )
    75  
    76  // All general log messages in this package should be routed through this logger.
    77  var logger = log.New("package", "status-go/cmd/statusd")
    78  
    79  func init() {
    80  	flag.Var(&configFiles, "c", "JSON configuration file(s). Multiple configuration files can be specified, and will be merged in occurrence order")
    81  }
    82  
    83  // nolint:gocyclo
    84  func main() {
    85  	colors := terminal.IsTerminal(int(os.Stdin.Fd()))
    86  	if err := logutils.OverrideRootLog(true, "ERROR", logutils.FileOptions{}, colors); err != nil {
    87  		stdlog.Fatalf("Error initializing logger: %v", err)
    88  	}
    89  
    90  	flag.Usage = printUsage
    91  	flag.Parse()
    92  	if flag.NArg() > 0 {
    93  		printUsage()
    94  		logger.Error("Extra args in command line: %v", flag.Args())
    95  		os.Exit(1)
    96  	}
    97  
    98  	opts := []params.Option{}
    99  
   100  	config, err := params.NewNodeConfigWithDefaultsAndFiles(
   101  		*dataDir,
   102  		uint64(*networkID),
   103  		opts,
   104  		configFiles,
   105  	)
   106  	if err != nil {
   107  		printUsage()
   108  		logger.Error(err.Error())
   109  		os.Exit(1)
   110  	}
   111  
   112  	// Use listenAddr if and only if explicitly provided in the arguments.
   113  	// The default value is set in params.NewNodeConfigWithDefaultsAndFiles().
   114  	if *listenAddr != "" {
   115  		config.ListenAddr = *listenAddr
   116  	}
   117  
   118  	// enable IPC RPC
   119  	if *ipcEnabled {
   120  		config.IPCEnabled = true
   121  		config.IPCFile = *ipcFile
   122  	}
   123  
   124  	// set up logging options
   125  	setupLogging(config)
   126  
   127  	// We want statusd to be distinct from StatusIM client.
   128  	config.Name = serverClientName
   129  
   130  	if *version {
   131  		printVersion(config)
   132  		return
   133  	}
   134  
   135  	backend := api.NewGethStatusBackend()
   136  	err = ImportAccount(*seedPhrase, backend)
   137  	if err != nil {
   138  		logger.Error("failed import account", "err", err)
   139  		return
   140  	}
   141  
   142  	wakuextservice := backend.StatusNode().WakuExtService()
   143  	if wakuextservice == nil {
   144  		logger.Error("wakuext not available")
   145  		return
   146  	}
   147  
   148  	wakuext := wakuextn.NewPublicAPI(wakuextservice)
   149  
   150  	// This will start the push notification server as well as
   151  	// the config is set to Enabled
   152  	_, err = wakuext.StartMessenger()
   153  	if err != nil {
   154  		logger.Error("failed to start messenger", "error", err)
   155  		return
   156  	}
   157  
   158  	logger.Info("Creating added contacts")
   159  
   160  	for i := 0; i < *nAddedContacts; i++ {
   161  		key, err := crypto.GenerateKey()
   162  		if err != nil {
   163  			logger.Error("failed generate key", err)
   164  			return
   165  		}
   166  
   167  		keyString := common.PubkeyToHex(&key.PublicKey)
   168  		_, err = wakuext.AddContact(context.Background(), &requests.AddContact{ID: keyString})
   169  		if err != nil {
   170  			logger.Error("failed Add contact", "err", err)
   171  			return
   172  		}
   173  	}
   174  
   175  	logger.Info("Creating contacts")
   176  
   177  	for i := 0; i < *nContacts; i++ {
   178  		key, err := crypto.GenerateKey()
   179  		if err != nil {
   180  			return
   181  		}
   182  
   183  		contact, err := protocol.BuildContactFromPublicKey(&key.PublicKey)
   184  		if err != nil {
   185  			return
   186  		}
   187  
   188  		_, err = wakuext.AddContact(context.Background(), &requests.AddContact{ID: contact.ID})
   189  		if err != nil {
   190  			return
   191  		}
   192  	}
   193  
   194  	logger.Info("Creating public chats")
   195  
   196  	for i := 0; i < *nPublicChats; i++ {
   197  		chat := protocol.CreatePublicChat(randomString(10), &testTimeSource{})
   198  		chat.SyncedTo = 0
   199  		chat.SyncedFrom = 0
   200  
   201  		err = wakuext.SaveChat(context.Background(), chat)
   202  		if err != nil {
   203  			return
   204  		}
   205  
   206  		var messages []*common.Message
   207  
   208  		for i := 0; i < *nMessages; i++ {
   209  			messages = append(messages, buildMessage(chat, i))
   210  
   211  		}
   212  
   213  		if len(messages) > 0 {
   214  			if err := wakuext.SaveMessages(context.Background(), messages); err != nil {
   215  				return
   216  			}
   217  		}
   218  
   219  	}
   220  
   221  	logger.Info("Creating communities", "num", *nCommunities)
   222  	for i := 0; i < *nCommunities; i++ {
   223  		request := requests.CreateCommunity{
   224  			Name:        randomString(10),
   225  			Description: randomString(30),
   226  			Color:       "#ffffff",
   227  			Membership:  protobuf.CommunityPermissions_MANUAL_ACCEPT,
   228  		}
   229  		_, err = wakuext.CreateCommunity(&request)
   230  		if err != nil {
   231  			logger.Error("failed to create community", "error", err)
   232  			return
   233  		}
   234  	}
   235  
   236  	logger.Info("Creating one to one chats")
   237  
   238  	for i := 0; i < *nOneToOneChats; i++ {
   239  		key, err := crypto.GenerateKey()
   240  		if err != nil {
   241  			return
   242  		}
   243  
   244  		keyString := common.PubkeyToHex(&key.PublicKey)
   245  		chat := protocol.CreateOneToOneChat(keyString, &key.PublicKey, &testTimeSource{})
   246  		chat.SyncedTo = 0
   247  		chat.SyncedFrom = 0
   248  		err = wakuext.SaveChat(context.Background(), chat)
   249  		if err != nil {
   250  			return
   251  		}
   252  		var messages []*common.Message
   253  
   254  		for i := 0; i < *nMessages; i++ {
   255  			messages = append(messages, buildMessage(chat, i))
   256  
   257  		}
   258  
   259  		if len(messages) > 0 {
   260  			if err := wakuext.SaveMessages(context.Background(), messages); err != nil {
   261  				return
   262  			}
   263  		}
   264  
   265  	}
   266  }
   267  
   268  func getDefaultDataDir() string {
   269  	if home := os.Getenv("HOME"); home != "" {
   270  		return filepath.Join(home, ".statusd")
   271  	}
   272  	return "./statusd-data"
   273  }
   274  
   275  func setupLogging(config *params.NodeConfig) {
   276  	if *logLevel != "" {
   277  		config.LogLevel = *logLevel
   278  	}
   279  
   280  	logSettings := logutils.LogSettings{
   281  		Enabled:         config.LogEnabled,
   282  		MobileSystem:    config.LogMobileSystem,
   283  		Level:           config.LogLevel,
   284  		File:            config.LogFile,
   285  		MaxSize:         config.LogMaxSize,
   286  		MaxBackups:      config.LogMaxBackups,
   287  		CompressRotated: config.LogCompressRotated,
   288  	}
   289  	colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd()))
   290  	if err := logutils.OverrideRootLogWithConfig(logSettings, colors); err != nil {
   291  		stdlog.Fatalf("Error initializing logger: %v", err)
   292  	}
   293  }
   294  
   295  // printVersion prints verbose output about version and config.
   296  func printVersion(config *params.NodeConfig) {
   297  	fmt.Println(strings.Title(config.Name))
   298  	fmt.Println("Version:", config.Version)
   299  	fmt.Println("Network ID:", config.NetworkID)
   300  	fmt.Println("Go Version:", runtime.Version())
   301  	fmt.Println("OS:", runtime.GOOS)
   302  	fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
   303  	fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
   304  
   305  	fmt.Println("Loaded Config: ", config)
   306  }
   307  
   308  func printUsage() {
   309  	usage := `
   310  Usage: statusd [options]
   311  Examples:
   312    statusd                                        # run regular Whisper node that joins Status network
   313    statusd -c ./default.json                      # run node with configuration specified in ./default.json file
   314    statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file
   315    statusd -c ./default.json -metrics             # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call
   316  
   317  Options:
   318  `
   319  	fmt.Fprint(os.Stderr, usage)
   320  	flag.PrintDefaults()
   321  }
   322  
   323  const pathWalletRoot = "m/44'/60'/0'/0"
   324  const pathEIP1581 = "m/43'/60'/1581'"
   325  const pathDefaultChat = pathEIP1581 + "/0'/0"
   326  const pathDefaultWallet = pathWalletRoot + "/0"
   327  
   328  var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
   329  
   330  func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) {
   331  	chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
   332  
   333  	defaultSettings := &settings.Settings{}
   334  	defaultSettings.KeyUID = generatedAccountInfo.KeyUID
   335  	defaultSettings.Address = types.HexToAddress(generatedAccountInfo.Address)
   336  	defaultSettings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
   337  
   338  	// Set chat key & name
   339  	name, err := alias.GenerateFromPublicKeyString(chatKeyString)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	defaultSettings.Name = name
   344  	defaultSettings.PublicKey = chatKeyString
   345  
   346  	defaultSettings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address)
   347  	defaultSettings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address)
   348  	defaultSettings.Mnemonic = mnemonic
   349  
   350  	signingPhrase, err := buildSigningPhrase()
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  	defaultSettings.SigningPhrase = signingPhrase
   355  
   356  	defaultSettings.SendPushNotifications = true
   357  	defaultSettings.InstallationID = uuid.New().String()
   358  	defaultSettings.UseMailservers = true
   359  
   360  	defaultSettings.PreviewPrivacy = true
   361  	defaultSettings.PeerSyncingEnabled = false
   362  	defaultSettings.Currency = "usd"
   363  	defaultSettings.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone
   364  	defaultSettings.LinkPreviewRequestEnabled = true
   365  
   366  	defaultSettings.TestNetworksEnabled = false
   367  
   368  	visibleTokens := make(map[string][]string)
   369  	visibleTokens["mainnet"] = []string{"SNT"}
   370  	visibleTokensJSON, err := json.Marshal(visibleTokens)
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  	visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
   375  	defaultSettings.WalletVisibleTokens = &visibleTokenJSONRaw
   376  
   377  	// TODO: fix this
   378  	networks := make([]map[string]string, 0)
   379  	networksJSON, err := json.Marshal(networks)
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	networkRawMessage := json.RawMessage(networksJSON)
   384  	defaultSettings.Networks = &networkRawMessage
   385  	defaultSettings.CurrentNetwork = "mainnet_rpc"
   386  
   387  	return defaultSettings, nil
   388  }
   389  
   390  func defaultNodeConfig(installationID string) (*params.NodeConfig, error) {
   391  	// Set mainnet
   392  	nodeConfig := &params.NodeConfig{}
   393  	nodeConfig.NetworkID = 1
   394  	nodeConfig.LogLevel = "ERROR"
   395  	nodeConfig.DataDir = api.DefaultDataDir
   396  	nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
   397  		Enabled: true,
   398  		URL:     "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938",
   399  	}
   400  
   401  	nodeConfig.Name = "StatusIM"
   402  	clusterConfig, err := params.LoadClusterConfigFromFleet("eth.prod")
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  	nodeConfig.ClusterConfig = *clusterConfig
   407  
   408  	nodeConfig.WalletConfig = params.WalletConfig{Enabled: true}
   409  	nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
   410  	nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true}
   411  	nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true}
   412  	nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true}
   413  	nodeConfig.WakuConfig = params.WakuConfig{
   414  		Enabled:     true,
   415  		LightClient: true,
   416  		MinimumPoW:  0.000001,
   417  	}
   418  
   419  	nodeConfig.ShhextConfig = params.ShhextConfig{
   420  		InstallationID:             installationID,
   421  		MaxMessageDeliveryAttempts: api.DefaultMaxMessageDeliveryAttempts,
   422  		MailServerConfirmations:    true,
   423  		VerifyTransactionURL:       "",
   424  		VerifyENSURL:               "",
   425  		VerifyENSContractAddress:   "",
   426  		VerifyTransactionChainID:   api.DefaultVerifyTransactionChainID,
   427  		DataSyncEnabled:            true,
   428  		PFSEnabled:                 true,
   429  	}
   430  
   431  	// TODO: check topics
   432  
   433  	return nodeConfig, nil
   434  }
   435  
   436  func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
   437  	backend.UpdateRootDataDir("./tmp")
   438  	manager := backend.AccountManager()
   439  	if err := manager.InitKeystore("./tmp"); err != nil {
   440  		return err
   441  	}
   442  	err := backend.OpenAccounts()
   443  	if err != nil {
   444  		logger.Error("failed open accounts", err)
   445  		return err
   446  	}
   447  	generator := manager.AccountsGenerator()
   448  	generatedAccountInfo, err := generator.ImportMnemonic(seedPhrase, "")
   449  	if err != nil {
   450  		return err
   451  	}
   452  
   453  	derivedAddresses, err := generator.DeriveAddresses(generatedAccountInfo.ID, paths)
   454  	if err != nil {
   455  		return err
   456  	}
   457  
   458  	_, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, "", paths)
   459  	if err != nil {
   460  		return err
   461  	}
   462  
   463  	account := multiaccounts.Account{
   464  		KeyUID:        generatedAccountInfo.KeyUID,
   465  		KDFIterations: dbsetup.ReducedKDFIterationsNumber,
   466  	}
   467  	settings, err := defaultSettings(generatedAccountInfo, derivedAddresses, &seedPhrase)
   468  	if err != nil {
   469  		return err
   470  	}
   471  
   472  	nodeConfig, err := defaultNodeConfig(settings.InstallationID)
   473  	if err != nil {
   474  		return err
   475  	}
   476  
   477  	walletDerivedAccount := derivedAddresses[pathDefaultWallet]
   478  	walletAccount := &accounts.Account{
   479  		PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
   480  		KeyUID:    generatedAccountInfo.KeyUID,
   481  		Address:   types.HexToAddress(walletDerivedAccount.Address),
   482  		ColorID:   "",
   483  		Wallet:    true,
   484  		Path:      pathDefaultWallet,
   485  		Name:      "Ethereum account",
   486  	}
   487  
   488  	chatDerivedAccount := derivedAddresses[pathDefaultChat]
   489  	chatAccount := &accounts.Account{
   490  		PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
   491  		KeyUID:    generatedAccountInfo.KeyUID,
   492  		Address:   types.HexToAddress(chatDerivedAccount.Address),
   493  		Name:      settings.Name,
   494  		Chat:      true,
   495  		Path:      pathDefaultChat,
   496  	}
   497  
   498  	fmt.Println(nodeConfig)
   499  	accounts := []*accounts.Account{walletAccount, chatAccount}
   500  	err = backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil)
   501  	if err != nil {
   502  		logger.Error("start node", err)
   503  		return err
   504  	}
   505  
   506  	return nil
   507  }
   508  
   509  var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
   510  
   511  func buildMessage(chat *protocol.Chat, count int) *common.Message {
   512  	key, err := crypto.GenerateKey()
   513  	if err != nil {
   514  		logger.Error("failed build message", err)
   515  		return nil
   516  	}
   517  
   518  	clock, timestamp := chat.NextClockAndTimestamp(&testTimeSource{})
   519  	clock += uint64(count)
   520  	message := common.NewMessage()
   521  	message.Text = fmt.Sprintf("test message %d", count)
   522  	message.ChatId = chat.ID
   523  	message.Clock = clock
   524  	message.Timestamp = timestamp
   525  	message.From = common.PubkeyToHex(&key.PublicKey)
   526  	data := []byte(uuid.New().String())
   527  	message.ID = types.HexBytes(crypto.Keccak256(data)).String()
   528  	message.WhisperTimestamp = clock
   529  	message.LocalChatID = chat.ID
   530  	message.ContentType = protobuf.ChatMessage_TEXT_PLAIN
   531  	switch chat.ChatType {
   532  	case protocol.ChatTypePublic, protocol.ChatTypeProfile:
   533  		message.MessageType = protobuf.MessageType_PUBLIC_GROUP
   534  	case protocol.ChatTypeOneToOne:
   535  		message.MessageType = protobuf.MessageType_ONE_TO_ONE
   536  	case protocol.ChatTypePrivateGroupChat:
   537  		message.MessageType = protobuf.MessageType_PRIVATE_GROUP
   538  	}
   539  
   540  	_ = message.PrepareContent("")
   541  	return message
   542  }
   543  
   544  func randomString(n int) string {
   545  	b := make([]rune, n)
   546  	for i := range b {
   547  		b[i] = letterRunes[rand.Intn(len(letterRunes))] // nolint: gosec
   548  	}
   549  	return string(b)
   550  }