github.com/status-im/status-go@v1.1.0/cmd/spiff-workflow/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "flag" 7 "fmt" 8 stdlog "log" 9 "os" 10 "os/signal" 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 gethmetrics "github.com/ethereum/go-ethereum/metrics" 21 22 "github.com/status-im/status-go/account/generator" 23 "github.com/status-im/status-go/api" 24 "github.com/status-im/status-go/common/dbsetup" 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/metrics" 28 nodemetrics "github.com/status-im/status-go/metrics/node" 29 "github.com/status-im/status-go/multiaccounts" 30 "github.com/status-im/status-go/multiaccounts/accounts" 31 "github.com/status-im/status-go/multiaccounts/settings" 32 "github.com/status-im/status-go/node" 33 "github.com/status-im/status-go/params" 34 "github.com/status-im/status-go/profiling" 35 "github.com/status-im/status-go/protocol" 36 "github.com/status-im/status-go/protocol/identity/alias" 37 waku2extn "github.com/status-im/status-go/services/wakuv2ext" 38 ) 39 40 const ( 41 serverClientName = "Statusd" 42 ) 43 44 var ( 45 configFiles configFlags 46 logLevel = flag.String("log", "INFO", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`) 47 logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors") 48 seedPhrase = flag.String("seed-phrase", "", "Seed phrase") 49 version = flag.Bool("version", false, "Print version and dump configuration") 50 apiModules = flag.String("api-modules", "wakuext,ext,waku,ens", "API modules to enable in the HTTP server") 51 pprofEnabled = flag.Bool("pprof", false, "Enable runtime profiling via pprof") 52 pprofPort = flag.Int("pprof-port", 52525, "Port for runtime profiling via pprof") 53 metricsEnabled = flag.Bool("metrics", false, "Expose ethereum metrics with debug_metrics jsonrpc call") 54 metricsPort = flag.Int("metrics-port", 9305, "Port for the Prometheus /metrics endpoint") 55 56 dataDir = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data") 57 networkID = flag.Int( 58 "network-id", 59 params.GoerliNetworkID, 60 fmt.Sprintf( 61 "A network ID: %d (Mainnet), %d (Goerli)", 62 params.MainNetworkID, params.GoerliNetworkID, 63 ), 64 ) 65 listenAddr = flag.String("addr", "", "address to bind listener to") 66 ) 67 68 // All general log messages in this package should be routed through this logger. 69 var logger = log.New("package", "status-go/cmd/statusd") 70 71 func init() { 72 flag.Var(&configFiles, "c", "JSON configuration file(s). Multiple configuration files can be specified, and will be merged in occurrence order") 73 } 74 75 // nolint:gocyclo 76 func main() { 77 colors := terminal.IsTerminal(int(os.Stdin.Fd())) 78 if err := logutils.OverrideRootLog(true, "ERROR", logutils.FileOptions{}, colors); err != nil { 79 stdlog.Fatalf("Error initializing logger: %v", err) 80 } 81 82 flag.Usage = printUsage 83 flag.Parse() 84 if flag.NArg() > 0 { 85 printUsage() 86 logger.Error("Extra args in command line: %v", flag.Args()) 87 os.Exit(1) 88 } 89 90 opts := []params.Option{} 91 92 config, err := params.NewNodeConfigWithDefaultsAndFiles( 93 *dataDir, 94 uint64(*networkID), 95 opts, 96 configFiles, 97 ) 98 if err != nil { 99 printUsage() 100 logger.Error(err.Error()) 101 os.Exit(1) 102 } 103 104 // Use listenAddr if and only if explicitly provided in the arguments. 105 // The default value is set in params.NewNodeConfigWithDefaultsAndFiles(). 106 if *listenAddr != "" { 107 config.ListenAddr = *listenAddr 108 } 109 110 if *logLevel != "" { 111 config.LogLevel = *logLevel 112 } 113 114 // set up logging options 115 setupLogging(config) 116 117 // We want statusd to be distinct from StatusIM client. 118 config.Name = serverClientName 119 120 if *version { 121 printVersion(config) 122 return 123 } 124 125 // Check if profiling shall be enabled. 126 if *pprofEnabled { 127 profiling.NewProfiler(*pprofPort).Go() 128 } 129 130 backend := api.NewGethStatusBackend() 131 err = ImportAccount(*seedPhrase, backend) 132 if err != nil { 133 logger.Error("failed import account", "err", err) 134 return 135 } 136 137 // handle interrupt signals 138 interruptCh := exitOnInterruptSignal(backend.StatusNode()) 139 140 // Start collecting metrics. Metrics can be enabled by providing `-metrics` flag 141 // or setting `gethmetrics.Enabled` to true during compilation time: 142 // https://github.com/status-im/go-ethereum/pull/76. 143 if *metricsEnabled || gethmetrics.Enabled { 144 go startNodeMetrics(interruptCh, backend.StatusNode()) 145 go gethmetrics.CollectProcessMetrics(3 * time.Second) 146 go metrics.NewMetricsServer(*metricsPort, gethmetrics.DefaultRegistry).Listen() 147 } 148 149 wakuextservice := backend.StatusNode().WakuV2ExtService() 150 if wakuextservice == nil { 151 logger.Error("wakuext not available") 152 return 153 } 154 155 wakuext := waku2extn.NewPublicAPI(wakuextservice) 156 157 messenger := wakuext.Messenger() 158 messenger.DisableStoreNodes() 159 // This will start the push notification server as well as 160 // the config is set to Enabled 161 _, err = wakuext.StartMessenger() 162 if err != nil { 163 logger.Error("failed to start messenger", "error", err) 164 return 165 } 166 167 retrieveMessagesLoop(messenger, 300*time.Millisecond) 168 169 } 170 171 func getDefaultDataDir() string { 172 if home := os.Getenv("HOME"); home != "" { 173 return filepath.Join(home, ".statusd") 174 } 175 return "./statusd-data" 176 } 177 178 func setupLogging(config *params.NodeConfig) { 179 logSettings := logutils.LogSettings{ 180 Enabled: config.LogEnabled, 181 MobileSystem: config.LogMobileSystem, 182 Level: config.LogLevel, 183 File: config.LogFile, 184 MaxSize: config.LogMaxSize, 185 MaxBackups: config.LogMaxBackups, 186 CompressRotated: config.LogCompressRotated, 187 } 188 colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd())) 189 if err := logutils.OverrideRootLogWithConfig(logSettings, colors); err != nil { 190 stdlog.Fatalf("Error initializing logger: %v", err) 191 } 192 } 193 194 // printVersion prints verbose output about version and config. 195 func printVersion(config *params.NodeConfig) { 196 fmt.Println(strings.Title(config.Name)) 197 fmt.Println("Version:", config.Version) 198 fmt.Println("Network ID:", config.NetworkID) 199 fmt.Println("Go Version:", runtime.Version()) 200 fmt.Println("OS:", runtime.GOOS) 201 fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) 202 fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) 203 204 fmt.Println("Loaded Config: ", config) 205 } 206 207 func printUsage() { 208 usage := ` 209 Usage: statusd [options] 210 Examples: 211 statusd # run regular Whisper node that joins Status network 212 statusd -c ./default.json # run node with configuration specified in ./default.json file 213 statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file 214 statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call 215 216 Options: 217 ` 218 fmt.Fprint(os.Stderr, usage) 219 flag.PrintDefaults() 220 } 221 222 const pathWalletRoot = "m/44'/60'/0'/0" 223 const pathEIP1581 = "m/43'/60'/1581'" 224 const pathDefaultChat = pathEIP1581 + "/0'/0" 225 const pathDefaultWallet = pathWalletRoot + "/0" 226 227 var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet} 228 229 func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*settings.Settings, error) { 230 chatKeyString := derivedAddresses[pathDefaultChat].PublicKey 231 232 defaultSettings := &settings.Settings{} 233 defaultSettings.KeyUID = generatedAccountInfo.KeyUID 234 defaultSettings.Address = types.HexToAddress(generatedAccountInfo.Address) 235 defaultSettings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address) 236 237 // Set chat key & name 238 name, err := alias.GenerateFromPublicKeyString(chatKeyString) 239 if err != nil { 240 return nil, err 241 } 242 defaultSettings.Name = name 243 defaultSettings.PublicKey = chatKeyString 244 245 defaultSettings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address) 246 defaultSettings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address) 247 defaultSettings.Mnemonic = mnemonic 248 249 signingPhrase, err := buildSigningPhrase() 250 if err != nil { 251 return nil, err 252 } 253 defaultSettings.SigningPhrase = signingPhrase 254 255 defaultSettings.SendPushNotifications = true 256 defaultSettings.InstallationID = uuid.New().String() 257 defaultSettings.UseMailservers = true 258 259 defaultSettings.PreviewPrivacy = true 260 defaultSettings.PeerSyncingEnabled = false 261 defaultSettings.Currency = "usd" 262 defaultSettings.ProfilePicturesVisibility = settings.ProfilePicturesVisibilityEveryone 263 defaultSettings.LinkPreviewRequestEnabled = true 264 265 defaultSettings.TestNetworksEnabled = false 266 267 visibleTokens := make(map[string][]string) 268 visibleTokens["mainnet"] = []string{"SNT"} 269 visibleTokensJSON, err := json.Marshal(visibleTokens) 270 if err != nil { 271 return nil, err 272 } 273 visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON) 274 defaultSettings.WalletVisibleTokens = &visibleTokenJSONRaw 275 276 // TODO: fix this 277 networks := make([]map[string]string, 0) 278 networksJSON, err := json.Marshal(networks) 279 if err != nil { 280 return nil, err 281 } 282 networkRawMessage := json.RawMessage(networksJSON) 283 defaultSettings.Networks = &networkRawMessage 284 defaultSettings.CurrentNetwork = "mainnet_rpc" 285 286 return defaultSettings, nil 287 } 288 289 func defaultNodeConfig(installationID string) (*params.NodeConfig, error) { 290 // Set mainnet 291 nodeConfig := ¶ms.NodeConfig{} 292 nodeConfig.NetworkID = 1 293 nodeConfig.LogLevel = "DEBUG" 294 nodeConfig.DataDir = api.DefaultDataDir 295 nodeConfig.HTTPEnabled = true 296 nodeConfig.HTTPPort = 8545 297 // FIXME: This should be taken from CLI flags. 298 nodeConfig.HTTPHost = "0.0.0.0" 299 // FIXME: This should be taken from CLI flags. 300 nodeConfig.HTTPVirtualHosts = []string{"localhost", "wakunode"} 301 nodeConfig.APIModules = *apiModules 302 // Disable to avoid errors about empty ClusterConfig.BootNodes. 303 nodeConfig.NoDiscovery = true 304 305 nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{ 306 Enabled: true, 307 URL: "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938", 308 } 309 310 nodeConfig.Name = "StatusIM" 311 clusterConfig, err := params.LoadClusterConfigFromFleet("status.prod") 312 if err != nil { 313 return nil, err 314 } 315 nodeConfig.ClusterConfig = *clusterConfig 316 317 nodeConfig.WalletConfig = params.WalletConfig{Enabled: true} 318 nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true} 319 nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true} 320 nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true} 321 nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true} 322 err = api.SetDefaultFleet(nodeConfig) 323 if err != nil { 324 return nil, err 325 } 326 327 nodeConfig.WakuV2Config = params.WakuV2Config{ 328 Enabled: true, 329 EnableDiscV5: true, 330 DiscoveryLimit: 20, 331 UDPPort: 9002, 332 } 333 334 nodeConfig.ShhextConfig = params.ShhextConfig{ 335 InstallationID: installationID, 336 MaxMessageDeliveryAttempts: api.DefaultMaxMessageDeliveryAttempts, 337 MailServerConfirmations: true, 338 VerifyTransactionURL: "", 339 VerifyENSURL: "", 340 VerifyENSContractAddress: "", 341 VerifyTransactionChainID: api.DefaultVerifyTransactionChainID, 342 DataSyncEnabled: true, 343 PFSEnabled: true, 344 } 345 346 // TODO: check topics 347 348 return nodeConfig, nil 349 } 350 351 func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error { 352 backend.UpdateRootDataDir(*dataDir) 353 manager := backend.AccountManager() 354 if err := manager.InitKeystore(*dataDir); err != nil { 355 return err 356 } 357 err := backend.OpenAccounts() 358 if err != nil { 359 logger.Error("failed open accounts", err) 360 return err 361 } 362 generator := manager.AccountsGenerator() 363 generatedAccountInfo, err := generator.ImportMnemonic(seedPhrase, "") 364 if err != nil { 365 logger.Error("failed import mnemonic", err) 366 return err 367 } 368 369 derivedAddresses, err := generator.DeriveAddresses(generatedAccountInfo.ID, paths) 370 if err != nil { 371 logger.Error("failed derive", err) 372 return err 373 } 374 375 var exist bool 376 _, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, "", paths) 377 if err != nil && err.Error() == "account already exists" { 378 exist = true 379 } else if err != nil { 380 logger.Error("failed store derive", err) 381 return err 382 } 383 384 account := multiaccounts.Account{ 385 KeyUID: generatedAccountInfo.KeyUID, 386 KDFIterations: dbsetup.ReducedKDFIterationsNumber, 387 } 388 settings, err := defaultSettings(generatedAccountInfo, derivedAddresses, &seedPhrase) 389 if err != nil { 390 return err 391 } 392 393 nodeConfig, err := defaultNodeConfig(settings.InstallationID) 394 if err != nil { 395 return err 396 } 397 398 walletDerivedAccount := derivedAddresses[pathDefaultWallet] 399 walletAccount := &accounts.Account{ 400 PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey), 401 KeyUID: generatedAccountInfo.KeyUID, 402 Address: types.HexToAddress(walletDerivedAccount.Address), 403 ColorID: "", 404 Wallet: true, 405 Path: pathDefaultWallet, 406 Name: "Ethereum account", 407 } 408 409 chatDerivedAccount := derivedAddresses[pathDefaultChat] 410 chatAccount := &accounts.Account{ 411 PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey), 412 KeyUID: generatedAccountInfo.KeyUID, 413 Address: types.HexToAddress(chatDerivedAccount.Address), 414 Name: settings.Name, 415 Chat: true, 416 Path: pathDefaultChat, 417 } 418 419 fmt.Println(nodeConfig) 420 accounts := []*accounts.Account{walletAccount, chatAccount} 421 if !exist { 422 return backend.StartNodeWithAccountAndInitialConfig(account, "", *settings, nodeConfig, accounts, nil) 423 } 424 return backend.StartNodeWithAccount(account, "", nodeConfig, nil) 425 } 426 427 func retrieveMessagesLoop(messenger *protocol.Messenger, tick time.Duration) { 428 ticker := time.NewTicker(tick) 429 defer ticker.Stop() 430 431 for { //nolint: gosimple 432 select { 433 case <-ticker.C: 434 _, err := messenger.RetrieveAll() 435 if err != nil { 436 logger.Error("failed to retrieve raw messages", "err", err) 437 continue 438 } 439 } 440 } 441 } 442 443 // exitOnInterruptSignal catches interrupt signal (SIGINT) and 444 // stops the node. It times out after 5 seconds 445 // if the node can not be stopped. 446 func exitOnInterruptSignal(statusNode *node.StatusNode) <-chan struct{} { 447 interruptCh := make(chan struct{}) 448 go func() { 449 sigChan := make(chan os.Signal, 1) 450 signal.Notify(sigChan, os.Interrupt) 451 defer signal.Stop(sigChan) 452 <-sigChan 453 close(interruptCh) 454 logger.Info("Got interrupt, shutting down...") 455 if err := statusNode.Stop(); err != nil { 456 logger.Error("Failed to stop node", "error", err) 457 os.Exit(1) 458 } 459 }() 460 return interruptCh 461 } 462 463 // startCollectingStats collects various stats about the node and other protocols like Whisper. 464 func startNodeMetrics(interruptCh <-chan struct{}, statusNode *node.StatusNode) { 465 logger.Info("Starting collecting node metrics") 466 467 gNode := statusNode.GethNode() 468 if gNode == nil { 469 logger.Error("Failed to run metrics because it could not get the node") 470 return 471 } 472 473 ctx, cancel := context.WithCancel(context.Background()) 474 defer cancel() 475 go func() { 476 // Try to subscribe and collect metrics. In case of an error, retry. 477 for { 478 if err := nodemetrics.SubscribeServerEvents(ctx, gNode); err != nil { 479 logger.Error("Failed to subscribe server events", "error", err) 480 } else { 481 // no error means that the subscription was terminated by purpose 482 return 483 } 484 485 time.Sleep(time.Second) 486 } 487 }() 488 489 <-interruptCh 490 }