Improve statusd CLI usage (#441)

This PR refactors CLI API, removes obsolete commands and splits status code into smaller pieces:

* get rid of subcommands API (no ./status <command>)
* get rid of custom cli app package
* use stdlib flag package for handling command line flags
* move cross-compilation / mobile related code to lib/ package
* move wnode command into separate binary (cmd/node-status, name is subject to discuss)
* remove faucet command as obsolete
* update/add docs/READMES/wikis for new command line flags

It makes statusd code much simpler and smaller, separates concerns (lib, wnode and statusd are different things).
This commit is contained in:
Ivan Daniluk 2017-11-03 23:07:13 +01:00 committed by Ivan Tomilov
parent 987cdd6221
commit 4536e99275
15 changed files with 409 additions and 597 deletions

6
.gitignore vendored
View File

@ -5,6 +5,12 @@
# git config --global core.excludesfile ~/.gitignore_global # git config --global core.excludesfile ~/.gitignore_global
/statusd-data /statusd-data
/cmd/statusd/statusd-data
/cmd/statusd/statusd
/wnode-status-data
/cmd/wnode-status/wnode-status-data
/cmd/wnode-status/wnode-status
/tmp /tmp
*/**/*un~ */**/*un~

View File

@ -1,6 +1,9 @@
.PHONY: statusgo all test xgo clean help .PHONY: statusgo all test xgo clean help
.PHONY: statusgo-android statusgo-ios .PHONY: statusgo-android statusgo-ios
help: ##@other Show this help
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
include ./static/tools/mk/lint.mk include ./static/tools/mk/lint.mk
GOBIN = build/bin GOBIN = build/bin
@ -28,32 +31,32 @@ HELP_FUN = \
print "\n"; \ print "\n"; \
} }
help: ##@other Show this help
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
# Main targets # Main targets
UNIT_TEST_PACKAGES := $(shell go list ./... | grep -v /vendor | grep -v /e2e | grep -v /cmd) UNIT_TEST_PACKAGES := $(shell go list ./... | grep -v /vendor | grep -v /e2e | grep -v /cmd)
statusgo: ##@build Build status-go as statusd server statusgo: ##@build Build status-go as statusd server
build/env.sh go build -i -o $(GOBIN)/statusd -v $(shell build/testnet-flags.sh) ./cmd/statusd build/env.sh go build -i -o $(GOBIN)/statusd -v $(shell build/testnet-flags.sh) ./cmd/statusd
@echo "\nCompilation done.\nRun \"build/bin/statusd help\" to view available commands." @echo "\nCompilation done.\nRun \"build/bin/statusd -h\" to view available commands."
wnode-status: ##@build Build wnode-status (Whisper 5 debug tool)
build/env.sh go build -i -o $(GOBIN)/wnode-status -v $(shell build/testnet-flags.sh) ./cmd/wnode-status
statusgo-cross: statusgo-android statusgo-ios statusgo-cross: statusgo-android statusgo-ios
@echo "Full cross compilation done." @echo "Full cross compilation done."
@ls -ld $(GOBIN)/statusgo-* @ls -ld $(GOBIN)/statusgo-*
statusgo-android: xgo ##@cross-compile Build status-go for Android statusgo-android: xgo ##@cross-compile Build status-go for Android
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=android-16/aar -v $(shell build/testnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=android-16/aar -v $(shell build/testnet-flags.sh) ./lib
@echo "Android cross compilation done." @echo "Android cross compilation done."
statusgo-ios: xgo ##@cross-compile Build status-go for iOS statusgo-ios: xgo ##@cross-compile Build status-go for iOS
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/testnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/testnet-flags.sh) ./lib
@echo "iOS framework cross compilation done." @echo "iOS framework cross compilation done."
statusgo-ios-simulator: xgo ##@cross-compile Build status-go for iOS Simulator statusgo-ios-simulator: xgo ##@cross-compile Build status-go for iOS Simulator
@build/env.sh docker pull farazdagi/xgo-ios-simulator @build/env.sh docker pull farazdagi/xgo-ios-simulator
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/testnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/testnet-flags.sh) ./lib
@echo "iOS framework cross compilation done." @echo "iOS framework cross compilation done."
xgo: xgo:
@ -66,15 +69,15 @@ statusgo-mainnet:
@echo "Run \"build/bin/statusgo\" to view available commands" @echo "Run \"build/bin/statusgo\" to view available commands"
statusgo-android-mainnet: xgo statusgo-android-mainnet: xgo
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=android-16/aar -v $(shell build/mainnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=android-16/aar -v $(shell build/mainnet-flags.sh) ./lib
@echo "Android cross compilation done (mainnet)." @echo "Android cross compilation done (mainnet)."
statusgo-ios-mainnet: xgo statusgo-ios-mainnet: xgo
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./lib
@echo "iOS framework cross compilation done (mainnet)." @echo "iOS framework cross compilation done (mainnet)."
statusgo-ios-simulator-mainnet: xgo statusgo-ios-simulator-mainnet: xgo
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./lib
@echo "iOS framework cross compilation done (mainnet)." @echo "iOS framework cross compilation done (mainnet)."
generate: ##@other Regenerate assets and other auto-generated stuff generate: ##@other Regenerate assets and other auto-generated stuff
@ -109,7 +112,7 @@ test-e2e: ##@tests Run e2e tests
build/env.sh go test -timeout 20m ./e2e/rpc/... -network=$(networkid) build/env.sh go test -timeout 20m ./e2e/rpc/... -network=$(networkid)
build/env.sh go test -timeout 20m ./e2e/whisper/... -network=$(networkid) build/env.sh go test -timeout 20m ./e2e/whisper/... -network=$(networkid)
build/env.sh go test -timeout 10m ./e2e/transactions/... -network=$(networkid) build/env.sh go test -timeout 10m ./e2e/transactions/... -network=$(networkid)
build/env.sh go test -timeout 40m ./cmd/statusd -network=$(networkid) build/env.sh go test -timeout 40m ./lib -network=$(networkid)
ci: lint mock-install mock test-unit test-e2e ##@tests Run all linters and tests at once ci: lint mock-install mock test-unit test-e2e ##@tests Run all linters and tests at once

View File

@ -1,66 +0,0 @@
package main
import (
"fmt"
"github.com/status-im/status-go/geth/params"
"gopkg.in/urfave/cli.v1"
)
var (
faucetCommand = cli.Command{
Action: faucetCommandHandler,
Name: "faucet",
Usage: "Starts faucet node (light node used by faucet service to request Ether)",
Flags: []cli.Flag{
HTTPPortFlag,
HTTPEnabledFlag,
},
}
)
// faucetCommandHandler handles `statusd faucet` command
func faucetCommandHandler(ctx *cli.Context) error {
config, err := parseFaucetCommandConfig(ctx)
if err != nil {
return fmt.Errorf("can not parse config: %v", err)
}
fmt.Println("Starting Status Faucet node..")
if err = statusAPI.StartNode(config); err != nil {
return err
}
// wait till node has been stopped
node, err := statusAPI.NodeManager().Node()
if err != nil {
return nil
}
node.Wait()
return nil
}
// parseFaucetCommandConfig parses incoming CLI options and returns node configuration object
func parseFaucetCommandConfig(ctx *cli.Context) (*params.NodeConfig, error) {
nodeConfig, err := makeNodeConfig(ctx)
if err != nil {
return nil, err
}
// select sub-protocols
nodeConfig.LightEthConfig.Enabled = true
nodeConfig.WhisperConfig.Enabled = false
nodeConfig.SwarmConfig.Enabled = false
// RPC configuration
nodeConfig.APIModules = "eth"
nodeConfig.HTTPHost = "0.0.0.0" // allow to connect from anywhere
nodeConfig.HTTPPort = ctx.Int(HTTPPortFlag.Name)
nodeConfig.RPCEnabled = ctx.Bool(HTTPEnabledFlag.Name)
// extra options
nodeConfig.BootClusterConfig.Enabled = true
return nodeConfig, nil
}

View File

@ -1,68 +0,0 @@
package main
import (
"fmt"
"github.com/status-im/status-go/geth/params"
"gopkg.in/urfave/cli.v1"
)
var (
lesCommand = cli.Command{
Action: lesCommandHandler,
Name: "les",
Usage: "Starts Light Ethereum node",
Flags: []cli.Flag{
WhisperEnabledFlag,
SwarmEnabledFlag,
HTTPEnabledFlag,
HTTPPortFlag,
IPCEnabledFlag,
},
}
)
// lesCommandHandler handles `statusd les` command
func lesCommandHandler(ctx *cli.Context) error {
config, err := parseLESCommandConfig(ctx)
if err != nil {
return fmt.Errorf("can not parse config: %v", err)
}
fmt.Println("Starting Light Status node..")
if err = statusAPI.StartNode(config); err != nil {
return err
}
// wait till node has been stopped
node, err := statusAPI.NodeManager().Node()
if err != nil {
return nil
}
node.Wait()
return nil
}
// parseLESCommandConfig parses incoming CLI options and returns node configuration object
func parseLESCommandConfig(ctx *cli.Context) (*params.NodeConfig, error) {
nodeConfig, err := makeNodeConfig(ctx)
if err != nil {
return nil, err
}
// Enabled sub-protocols
nodeConfig.LightEthConfig.Enabled = true
nodeConfig.RPCEnabled = ctx.Bool(HTTPEnabledFlag.Name)
nodeConfig.WhisperConfig.Enabled = ctx.Bool(WhisperEnabledFlag.Name)
nodeConfig.SwarmConfig.Enabled = ctx.Bool(SwarmEnabledFlag.Name)
// RPC configuration
if !ctx.Bool(HTTPEnabledFlag.Name) {
nodeConfig.HTTPHost = "" // HTTP RPC is disabled
}
nodeConfig.HTTPPort = ctx.Int(HTTPPortFlag.Name)
nodeConfig.IPCEnabled = ctx.Bool(IPCEnabledFlag.Name)
return nodeConfig, nil
}

View File

@ -1,174 +1,142 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"log"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings"
"github.com/status-im/status-go/geth/api" "github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
"gopkg.in/urfave/cli.v1"
) )
var ( var (
gitCommit = "rely on linker: -ldflags -X main.GitCommit" gitCommit = "N/A" // rely on linker: -ldflags -X main.GitCommit"
buildStamp = "rely on linker: -ldflags -X main.buildStamp" buildStamp = "N/A" // rely on linker: -ldflags -X main.buildStamp"
app = makeApp(gitCommit)
statusAPI = api.NewStatusAPI()
) )
var ( var (
// ProdModeFlag is whether we need dev or production settings prodMode = flag.Bool("production", false, "Whether production settings should be loaded")
ProdModeFlag = cli.BoolFlag{ nodeKeyFile = flag.String("nodekey", "", "P2P node key file (private key)")
Name: "production", dataDir = flag.String("datadir", params.DataDir, "Data directory for the databases and keystore")
Usage: "Whether production settings should be loaded", networkID = flag.Int("networkid", params.RopstenNetworkID, "Network identifier (integer, 1=Homestead, 3=Ropsten, 4=Rinkeby, 777=StatusChain)")
} whisperEnabled = flag.Bool("shh", false, "SHH protocol enabled")
swarmEnabled = flag.Bool("swarm", false, "Swarm protocol enabled")
// NodeKeyFileFlag is a node key file to be used as node's private key httpEnabled = flag.Bool("http", false, "HTTP RPC endpoint enabled (default: false)")
NodeKeyFileFlag = cli.StringFlag{ httpPort = flag.Int("httpport", params.HTTPPort, "HTTP RPC server's listening port")
Name: "nodekey", ipcEnabled = flag.Bool("ipc", false, "IPC RPC endpoint enabled")
Usage: "P2P node key file (private key)", logLevel = flag.String("log", "INFO", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
} logFile = flag.String("logfile", "", "Path to the log file")
version = flag.Bool("version", false, "Print version")
// DataDirFlag defines data directory for the node
DataDirFlag = cli.StringFlag{
Name: "datadir",
Usage: "Data directory for the databases and keystore",
Value: params.DataDir,
}
// NetworkIDFlag defines network ID
NetworkIDFlag = cli.IntFlag{
Name: "networkid",
Usage: "Network identifier (integer, 1=Homestead, 3=Ropsten, 4=Rinkeby)",
Value: params.RopstenNetworkID,
}
// WhisperEnabledFlag flags whether Whisper is enabled or not
WhisperEnabledFlag = cli.BoolFlag{
Name: "shh",
Usage: "SHH protocol enabled",
}
// SwarmEnabledFlag flags whether Swarm is enabled or not
SwarmEnabledFlag = cli.BoolFlag{
Name: "swarm",
Usage: "Swarm protocol enabled",
}
// HTTPEnabledFlag defines whether HTTP RPC endpoint should be opened or not
HTTPEnabledFlag = cli.BoolFlag{
Name: "http",
Usage: "HTTP RPC enpoint enabled (default: false)",
}
// HTTPPortFlag defines HTTP RPC port to use (if HTTP RPC is enabled)
HTTPPortFlag = cli.IntFlag{
Name: "httpport",
Usage: "HTTP RPC server's listening port",
Value: params.HTTPPort,
}
// IPCEnabledFlag flags whether IPC is enabled or not
IPCEnabledFlag = cli.BoolFlag{
Name: "ipc",
Usage: "IPC RPC enpoint enabled",
}
// LogLevelFlag defines a log reporting level
LogLevelFlag = cli.StringFlag{
Name: "log",
Usage: `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`,
Value: "",
}
// LogFileFlag defines a log filename
LogFileFlag = cli.StringFlag{
Name: "logfile",
Usage: `Path to the log file`,
Value: "",
}
) )
func init() {
// setup the app
app.Action = cli.ShowAppHelp
app.HideVersion = true // separate command prints version
app.Commands = []cli.Command{
versionCommand,
faucetCommand,
lesCommand,
wnodeCommand,
}
app.Flags = []cli.Flag{
ProdModeFlag,
NodeKeyFileFlag,
DataDirFlag,
NetworkIDFlag,
LogLevelFlag,
LogFileFlag,
}
app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
return nil
}
app.After = func(ctx *cli.Context) error {
return nil
}
}
func main() { func main() {
if err := app.Run(os.Args); err != nil { flag.Usage = printUsage
fmt.Fprintln(os.Stderr, err) flag.Parse()
os.Exit(1)
}
}
// makeApp creates an app with sane defaults. config, err := makeNodeConfig()
func makeApp(gitCommit string) *cli.App { if err != nil {
app := cli.NewApp() log.Fatalf("Making config failed: %v", err)
app.Name = filepath.Base(os.Args[0]) return
app.Author = ""
//app.Authors = nil
app.Email = ""
app.Version = params.Version
if gitCommit != "" {
app.Version += "-" + gitCommit[:8]
} }
app.Usage = "CLI for Status nodes management"
return app if *version {
printVersion(config, gitCommit, buildStamp)
return
}
backend := api.NewStatusBackend()
started, err := backend.StartNode(config)
if err != nil {
log.Fatalf("Node start failed: %v", err)
return
}
// wait till node is started
<-started
// wait till node has been stopped
node, err := backend.NodeManager().Node()
if err != nil {
log.Fatalf("Getting node failed: %v", err)
return
}
node.Wait()
} }
// makeNodeConfig parses incoming CLI options and returns node configuration object // makeNodeConfig parses incoming CLI options and returns node configuration object
func makeNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) { func makeNodeConfig() (*params.NodeConfig, error) {
nodeConfig, err := params.NewNodeConfig( devMode := !*prodMode
ctx.GlobalString(DataDirFlag.Name), nodeConfig, err := params.NewNodeConfig(*dataDir, uint64(*networkID), devMode)
ctx.GlobalUint64(NetworkIDFlag.Name),
!ctx.GlobalBool(ProdModeFlag.Name))
if err != nil { if err != nil {
return nil, err return nil, err
} }
nodeConfig.NodeKeyFile = ctx.GlobalString(NodeKeyFileFlag.Name) // TODO(divan): move this logic into params package
if *nodeKeyFile != "" {
nodeConfig.NodeKeyFile = *nodeKeyFile
}
if logLevel := ctx.GlobalString(LogLevelFlag.Name); logLevel != "" { if *logLevel != "" {
nodeConfig.LogLevel = logLevel nodeConfig.LogLevel = *logLevel
} }
if logFile := ctx.GlobalString(LogFileFlag.Name); logFile != "" { if *logFile != "" {
nodeConfig.LogFile = logFile nodeConfig.LogFile = *logFile
} }
nodeConfig.LightEthConfig.Enabled = true
nodeConfig.RPCEnabled = *httpEnabled
nodeConfig.WhisperConfig.Enabled = *whisperEnabled
nodeConfig.SwarmConfig.Enabled = *swarmEnabled
// RPC configuration
if !*httpEnabled {
nodeConfig.HTTPHost = "" // HTTP RPC is disabled
}
nodeConfig.HTTPPort = *httpPort
nodeConfig.IPCEnabled = *ipcEnabled
return nodeConfig, nil return nodeConfig, nil
} }
// printNodeConfig prints node config // printVersion prints verbose output about version and config.
func printNodeConfig(ctx *cli.Context) { func printVersion(config *params.NodeConfig, gitCommit, buildStamp string) {
nodeConfig, err := makeNodeConfig(ctx) if gitCommit != "" && len(gitCommit) > 8 {
if err != nil { params.Version += "-" + gitCommit[:8]
fmt.Printf("Loaded Config: failed (err: %v)", err)
return
} }
nodeConfig.LightEthConfig.Genesis = "SKIP"
fmt.Println("Loaded Config: ", nodeConfig) fmt.Println(strings.Title(params.ClientIdentifier))
fmt.Println("Version:", params.Version)
if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit)
}
if buildStamp != "" {
fmt.Println("Build Stamp:", buildStamp)
}
fmt.Println("Network Id:", config.NetworkID)
fmt.Println("Go Version:", runtime.Version())
fmt.Println("OS:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
config.LightEthConfig.Genesis = "SKIP"
fmt.Println("Loaded Config: ", config)
}
func printUsage() {
fmt.Fprintln(os.Stderr, "Usage: statusd [options]")
fmt.Fprintf(os.Stderr, `
Examples:
statusd # run status node with defaults
statusd -networkid 4 # run node on Rinkeby network
statusd -datadir /dir # specify different dir for data
statusd -ipc # enable IPC for usage with "geth attach"
Options:
`)
flag.PrintDefaults()
} }

View File

@ -1,41 +0,0 @@
package main
import (
"fmt"
"os"
"runtime"
"strings"
"github.com/status-im/status-go/geth/params"
"gopkg.in/urfave/cli.v1"
)
var (
versionCommand = cli.Command{
Action: versionCommandHandler,
Name: "version",
Usage: "Print app version",
}
)
// versionCommandHandler displays app version
func versionCommandHandler(ctx *cli.Context) error {
fmt.Println(strings.Title(params.ClientIdentifier))
fmt.Println("Version:", params.Version)
if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit)
}
if buildStamp != "" {
fmt.Println("Build Stamp:", buildStamp)
}
fmt.Println("Network Id:", ctx.GlobalInt(NetworkIDFlag.Name))
fmt.Println("Go Version:", runtime.Version())
fmt.Println("OS:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
printNodeConfig(ctx)
return nil
}

View File

@ -1,269 +0,0 @@
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
}

113
cmd/wnode-status/config.go Normal file
View File

@ -0,0 +1,113 @@
package main
import (
"errors"
"fmt"
"path/filepath"
"github.com/status-im/status-go/geth/params"
)
// makeNodeConfig creates node configuration object from flags
func makeNodeConfig() (*params.NodeConfig, error) {
devMode := !*prodMode
nodeConfig, err := params.NewNodeConfig(*dataDir, uint64(*networkID), devMode)
if err != nil {
return nil, err
}
// TODO(divan): move this logic into params package?
if *nodeKeyFile != "" {
nodeConfig.NodeKeyFile = *nodeKeyFile
}
// disable log
nodeConfig.LogLevel = "CRIT"
nodeConfig.LogFile = ""
// disable les and swarm for wnode
nodeConfig.LightEthConfig.Enabled = false
nodeConfig.SwarmConfig.Enabled = false
nodeConfig.RPCEnabled = *httpEnabled
// whisper configuration
whisperConfig := nodeConfig.WhisperConfig
whisperConfig.Enabled = true
whisperConfig.IdentityFile = *identity
whisperConfig.PasswordFile = *password
whisperConfig.EchoMode = *echo
whisperConfig.BootstrapNode = *bootstrap
whisperConfig.ForwarderNode = *forward
whisperConfig.NotificationServerNode = *notify
whisperConfig.MailServerNode = *mailserver
whisperConfig.Port = *port
whisperConfig.TTL = *ttl
whisperConfig.MinimumPoW = *pow
if whisperConfig.MailServerNode && whisperConfig.PasswordFile == "" {
return nil, errors.New("mail server requires -password to be specified")
}
if whisperConfig.NotificationServerNode && whisperConfig.IdentityFile == "" {
return nil, errors.New("notification server requires either -identity file to be specified")
}
if whisperConfig.PasswordFile != "" {
if err := verifyPasswordFile(whisperConfig); err != nil {
return nil, fmt.Errorf("read password file: %v", err)
}
}
if whisperConfig.IdentityFile != "" {
if err := verifyIdentityFile(whisperConfig); err != nil {
return nil, fmt.Errorf("read identity file: %v", err)
}
}
// firebase configuration
firebaseConfig := whisperConfig.FirebaseConfig
firebaseConfig.AuthorizationKeyFile = *firebaseAuth
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 !*httpEnabled {
nodeConfig.HTTPHost = "" // HTTP RPC is disabled
}
nodeConfig.HTTPPort = *httpPort
nodeConfig.IPCEnabled = *ipcEnabled
return nodeConfig, nil
}
// verifyPasswordFile verifies that we can load password file
func verifyPasswordFile(config *params.WhisperConfig) error {
// TODO(divan): why do we need it here?
absPath, err := filepath.Abs(config.PasswordFile)
if err != nil {
return err
}
config.PasswordFile = absPath
_, err = config.ReadPasswordFile()
return err
}
// verifyIdentityFile verifies that we can load identity file
func verifyIdentityFile(config *params.WhisperConfig) error {
// TODO(divan): why do we need it here?
absPath, err := filepath.Abs(config.IdentityFile)
if err != nil {
return err
}
config.IdentityFile = absPath
_, err = config.ReadIdentityFile()
return err
}

84
cmd/wnode-status/main.go Normal file
View File

@ -0,0 +1,84 @@
package main
import (
"flag"
"fmt"
"log"
"github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/params"
)
var (
prodMode = flag.Bool("production", false, "Whether production settings should be loaded")
nodeKeyFile = flag.String("nodekey", "", "P2P node key file (private key)")
dataDir = flag.String("datadir", "wnode-status-data", "Data directory for the databases and keystore")
networkID = flag.Int("networkid", params.RopstenNetworkID, "Network identifier (integer, 1=Homestead, 3=Ropsten, 4=Rinkeby)")
httpEnabled = flag.Bool("http", false, "HTTP RPC enpoint enabled (default: false)")
httpPort = flag.Int("httpport", params.HTTPPort, "HTTP RPC server's listening port")
ipcEnabled = flag.Bool("ipc", false, "IPC RPC enpoint enabled")
// wnode specific flags
echo = flag.Bool("echo", true, "Echo mode, prints some arguments for diagnostics")
bootstrap = flag.Bool("bootstrap", true, "Don't actively connect to peers, wait for incoming connections")
notify = flag.Bool("notify", false, "Node is capable of sending Push Notifications")
forward = flag.Bool("forward", false, "Only forward messages, neither send nor decrypt messages")
mailserver = flag.Bool("mailserver", false, "Delivers expired messages on demand")
identity = flag.String("identity", "", "Protocol identity file (private key used for asymmetric encryption)")
password = flag.String("password", "", "Password file (password is used for symmetric encryption)")
port = flag.Int("port", params.WhisperPort, "Whisper node's listening port")
pow = flag.Float64("pow", params.WhisperMinimumPoW, "PoW for messages to be added to queue, in float format")
ttl = flag.Int("ttl", params.WhisperTTL, "Time to live for messages, in seconds")
injectAccounts = flag.Bool("injectaccounts", true, "Whether test account should be injected or not")
firebaseAuth = flag.String("firebaseauth", "", "FCM Authorization Key used for sending Push Notifications")
)
func main() {
flag.Parse()
config, err := makeNodeConfig()
if err != nil {
log.Fatalf("Making config failed: %v", err)
}
printHeader(config)
if *injectAccounts {
if err := LoadTestAccounts(config.DataDir); err != nil {
log.Fatalf("Failed to load test accounts: %v", err)
}
}
backend := api.NewStatusBackend()
started, err := backend.StartNode(config)
if err != nil {
log.Fatalf("Node start failed: %v", err)
return
}
// wait till node is started
<-started
if *injectAccounts {
if err := InjectTestAccounts(backend.NodeManager()); err != nil {
log.Fatalf("Failed to inject accounts: %v", err)
}
}
// wait till node has been stopped
node, err := backend.NodeManager().Node()
if err != nil {
log.Fatalf("Getting node failed: %v", err)
return
}
node.Wait()
}
// printHeader prints command header
func printHeader(config *params.NodeConfig) {
fmt.Println("Starting Whisper/5 node..")
if config.WhisperConfig.EchoMode {
fmt.Printf("Whisper Config: %s\n", config.WhisperConfig)
}
}

View File

@ -0,0 +1,69 @@
package main
import (
"fmt"
"path/filepath"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/common"
)
// LoadTestAccounts loads public key files for test accounts
func LoadTestAccounts(dataDir string) error {
files := []string{"test-account1.pk", "test-account2.pk"}
dir := filepath.Join(dataDir, "keystore")
for _, filename := range files {
if err := common.ImportTestAccount(dir, filename); err != nil {
return err
}
}
return nil
}
// InjectTestAccounts injects test accounts into running node
func InjectTestAccounts(node common.NodeManager) error {
testConfig, err := common.LoadTestConfig()
if err != nil {
return err
}
if err = injectAccountIntoWhisper(node, testConfig.Account1.Address,
testConfig.Account1.Password); err != nil {
return err
}
if err = injectAccountIntoWhisper(node, testConfig.Account2.Address,
testConfig.Account2.Password); err != nil {
return err
}
return nil
}
// injectAccountIntoWhisper adds key pair into Whisper. Similar to Select/Login,
// but allows multiple accounts to be injected.
func injectAccountIntoWhisper(node common.NodeManager, address, password string) error {
keyStore, err := node.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 := node.WhisperService()
if err != nil {
return err
}
if _, err = whisperService.AddKeyPair(accountKey.PrivateKey); err != nil {
return err
}
return nil
}

13
lib/main.go Normal file
View File

@ -0,0 +1,13 @@
package main
import "github.com/status-im/status-go/geth/api"
var statusAPI = api.NewStatusAPI()
// Technically this package supposed to be a lib for
// cross-compilation and usage with Android/iOS, but
// without main it produces cryptic errors.
// TODO(divan): investigate the cause of the errors
// and change this package to be a library if possible.
func main() {
}

View File

@ -10,9 +10,9 @@ describe('Whisper Tests', function () {
node1.setProvider(new web3.providers.HttpProvider('http://localhost:8645')); node1.setProvider(new web3.providers.HttpProvider('http://localhost:8645'));
node2.setProvider(new web3.providers.HttpProvider('http://localhost:8745')); node2.setProvider(new web3.providers.HttpProvider('http://localhost:8745'));
console.log('Node is expected: statusd --datadir app1 wnode --http --httpport 8645'); console.log('Node is expected: wnode-status -datadir app1 wnode -http -httpport 8645');
console.log('Node is expected: statusd --datadir app2 wnode --http --httpport 8745'); console.log('Node is expected: wnode-status -datadir app2 wnode -http -httpport 8745');
console.log('Node is expected: statusd --datadir wnode1 wnode --notify --injectaccounts=false --identity ./static/keys/wnodekey --firebaseauth ./static/keys/firebaseauthkey'); console.log('Node is expected: wnode-status -datadir wnode1 wnode -notify -injectaccounts=false -identity ./static/keys/wnodekey -firebaseauth ./static/keys/firebaseauthkey');
// some common vars // some common vars
var topic1 = '0xdeadbeef'; // each topic 4 bytes, as hex var topic1 = '0xdeadbeef'; // each topic 4 bytes, as hex