package main import ( "errors" "fmt" "path/filepath" "github.com/status-im/status-go/geth/account" "github.com/status-im/status-go/geth/common" "github.com/status-im/status-go/geth/params" "gopkg.in/urfave/cli.v1" ) var ( // WhisperEchoModeFlag enables/disables Echo mode (arguments are printed for diagnostics) WhisperEchoModeFlag = cli.BoolTFlag{ Name: "echo", Usage: "Echo mode, prints some arguments for diagnostics (default: true)", } // WhisperBootstrapNodeFlag marks node as not actively listening for incoming connections WhisperBootstrapNodeFlag = cli.BoolTFlag{ Name: "bootstrap", Usage: "Don't actively connect to peers, wait for incoming connections (default: true)", } // WhisperNotificationServerNodeFlag enables/disables Push Notifications services WhisperNotificationServerNodeFlag = cli.BoolFlag{ Name: "notify", Usage: "Node is capable of sending Push Notifications", } // WhisperForwarderNodeFlag enables/disables message forwarding // (when neither sends nor decrypts envelopes, just forwards them) WhisperForwarderNodeFlag = cli.BoolFlag{ Name: "forward", Usage: "Only forward messages, neither send nor decrypt messages", } // WhisperMailserverNodeFlag enables/disables Inboxing services WhisperMailserverNodeFlag = cli.BoolFlag{ Name: "mailserver", Usage: "Delivers expired messages on demand", } // WhisperIdentityFile is path to file containing private key of the node (for asymmetric encryption) WhisperIdentityFile = cli.StringFlag{ Name: "identity", Usage: "Protocol identity file (private key used for asymmetric encryption)", } // WhisperPasswordFile is password used to do a symmetric encryption WhisperPasswordFile = cli.StringFlag{ Name: "password", Usage: "Password file (password is used for symmetric encryption)", } // WhisperPortFlag defines port on which Whisper protocol is listening WhisperPortFlag = cli.IntFlag{ Name: "port", Usage: "Whisper node's listening port", Value: params.WhisperPort, } // WhisperPoWFlag is the minimum PoW required by the node WhisperPoWFlag = cli.Float64Flag{ Name: "pow", Usage: "PoW for messages to be added to queue, in float format", Value: params.WhisperMinimumPoW, } // WhisperTTLFlag defines node's default TTL for envelopes WhisperTTLFlag = cli.IntFlag{ Name: "ttl", Usage: "Time to live for messages, in seconds", Value: params.WhisperTTL, } // WhisperInjectTestAccounts if set, then test accounts will be imported // into node's key store, and then will be injected as key pairs (identities) // into the Whisper as well. WhisperInjectTestAccounts = cli.BoolTFlag{ Name: "injectaccounts", Usage: "Whether test account should be injected or not (default: true)", } // FirebaseAuthorizationKey path to file containing FCM password FirebaseAuthorizationKey = cli.StringFlag{ Name: "firebaseauth", Usage: "FCM Authorization Key used for sending Push Notifications", } ) var ( wnodeCommand = cli.Command{ Action: wnode, Name: "wnode", Usage: "Starts Whisper/5 node", Flags: []cli.Flag{ WhisperEchoModeFlag, WhisperBootstrapNodeFlag, WhisperNotificationServerNodeFlag, WhisperForwarderNodeFlag, WhisperMailserverNodeFlag, WhisperIdentityFile, WhisperPasswordFile, WhisperPoWFlag, WhisperPortFlag, WhisperTTLFlag, WhisperInjectTestAccounts, FirebaseAuthorizationKey, HTTPEnabledFlag, HTTPPortFlag, }, } ) // version displays app version func wnode(ctx *cli.Context) error { config, err := makeWhisperNodeConfig(ctx) if err != nil { return fmt.Errorf("can not parse config: %v", err) } wnodePrintHeader(config) // import test accounts if ctx.BoolT(WhisperInjectTestAccounts.Name) { if err = common.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account1.pk"); err != nil { return err } if err = common.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account2.pk"); err != nil { return err } } if err := statusAPI.StartNode(config); err != nil { return err } // inject test accounts into Whisper if ctx.BoolT(WhisperInjectTestAccounts.Name) { testConfig, _ := common.LoadTestConfig() if err = injectAccountIntoWhisper(testConfig.Account1.Address, testConfig.Account1.Password); err != nil { return err } if err = injectAccountIntoWhisper(testConfig.Account2.Address, testConfig.Account2.Password); err != nil { return err } } // wait till node has been stopped node, err := statusAPI.NodeManager().Node() if err != nil { return nil } node.Wait() return nil } // wnodePrintHeader prints command header func wnodePrintHeader(nodeConfig *params.NodeConfig) { fmt.Println("Starting Whisper/5 node..") whisperConfig := nodeConfig.WhisperConfig if whisperConfig.EchoMode { fmt.Printf("Whisper Config: %s\n", whisperConfig) } } // makeWhisperNodeConfig parses incoming CLI options and returns node configuration object func makeWhisperNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) { nodeConfig, err := makeNodeConfig(ctx) if err != nil { return nil, err } nodeConfig.LightEthConfig.Enabled = false whisperConfig := nodeConfig.WhisperConfig whisperConfig.Enabled = true whisperConfig.IdentityFile = ctx.String(WhisperIdentityFile.Name) whisperConfig.PasswordFile = ctx.String(WhisperPasswordFile.Name) whisperConfig.EchoMode = ctx.BoolT(WhisperEchoModeFlag.Name) whisperConfig.BootstrapNode = ctx.BoolT(WhisperBootstrapNodeFlag.Name) whisperConfig.ForwarderNode = ctx.Bool(WhisperForwarderNodeFlag.Name) whisperConfig.NotificationServerNode = ctx.Bool(WhisperNotificationServerNodeFlag.Name) whisperConfig.MailServerNode = ctx.Bool(WhisperMailserverNodeFlag.Name) whisperConfig.Port = ctx.Int(WhisperPortFlag.Name) whisperConfig.TTL = ctx.Int(WhisperTTLFlag.Name) whisperConfig.MinimumPoW = ctx.Float64(WhisperPoWFlag.Name) if whisperConfig.MailServerNode && len(whisperConfig.PasswordFile) == 0 { return nil, errors.New("mail server requires --password to be specified") } if whisperConfig.NotificationServerNode && len(whisperConfig.IdentityFile) == 0 { return nil, errors.New("notification server requires either --identity file to be specified") } if len(whisperConfig.PasswordFile) > 0 { // make sure that we can load password file if whisperConfig.PasswordFile, err = filepath.Abs(whisperConfig.PasswordFile); err != nil { return nil, err } if _, err := whisperConfig.ReadPasswordFile(); err != nil { return nil, err } } if len(whisperConfig.IdentityFile) > 0 { // make sure that we can load identity file if whisperConfig.IdentityFile, err = filepath.Abs(whisperConfig.IdentityFile); err != nil { return nil, err } if _, err := whisperConfig.ReadIdentityFile(); err != nil { return nil, err } } firebaseConfig := whisperConfig.FirebaseConfig firebaseConfig.AuthorizationKeyFile = ctx.String(FirebaseAuthorizationKey.Name) if len(firebaseConfig.AuthorizationKeyFile) > 0 { // make sure authorization key can be loaded if firebaseConfig.AuthorizationKeyFile, err = filepath.Abs(firebaseConfig.AuthorizationKeyFile); err != nil { return nil, err } if _, err := firebaseConfig.ReadAuthorizationKeyFile(); err != nil { return nil, err } } // RPC configuration if !ctx.Bool(HTTPEnabledFlag.Name) { nodeConfig.HTTPHost = "" // HTTP RPC is disabled } nodeConfig.HTTPPort = ctx.Int(HTTPPortFlag.Name) return nodeConfig, nil } // injectAccountIntoWhisper adds key pair into Whisper. Similar to Select/Login, // but allows multiple accounts to be injected. func injectAccountIntoWhisper(address, password string) error { nodeManager := statusAPI.NodeManager() keyStore, err := nodeManager.AccountKeyStore() if err != nil { return err } acct, err := common.ParseAccountString(address) if err != nil { return account.ErrAddressToAccountMappingFailure } _, accountKey, err := keyStore.AccountDecryptedKey(acct, password) if err != nil { return fmt.Errorf("%s: %v", account.ErrAccountToKeyMappingFailure.Error(), err) } whisperService, err := nodeManager.WhisperService() if err != nil { return err } if _, err = whisperService.AddKeyPair(accountKey.PrivateKey); err != nil { return err } return nil }