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 := ¶ms.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 }