whisper: `statusd wnode` sub-command, closes #140

This commit is contained in:
Victor Farazdagi 2017-04-10 01:16:05 +03:00
parent 21344af199
commit 9a68fd211f
9 changed files with 324 additions and 90 deletions

View File

@ -5,7 +5,6 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"github.com/status-im/status-go/geth"
"github.com/status-im/status-go/geth/params"
@ -19,10 +18,14 @@ var (
)
var (
NodeKeyFileFlag = cli.StringFlag{
Name: "nodekey",
Usage: "P2P node key file (private key)",
}
DataDirFlag = cli.StringFlag{
Name: "datadir",
Usage: "Data directory for the databases and keystore",
Value: params.DefaultDataDir,
Value: params.DataDir,
}
NetworkIdFlag = cli.IntFlag{
Name: "networkid",
@ -48,7 +51,7 @@ var (
HTTPPortFlag = cli.IntFlag{
Name: "httpport",
Usage: "HTTP RPC server's listening port",
Value: params.DefaultHTTPPort,
Value: params.HTTPPort,
}
IPCEnabledFlag = cli.BoolFlag{
Name: "ipc",
@ -59,14 +62,6 @@ var (
Usage: `Log level, one of: ""ERROR", "WARNING", "INFO", "DEBUG", and "DETAIL"`,
Value: "INFO",
}
TestAccountKey = cli.StringFlag{
Name: "accountkey",
Usage: "Test account PK (will be loaded into accounts cache, and injected to Whisper)",
}
TestAccountPasswd = cli.StringFlag{
Name: "accountpasswd",
Usage: "Test account password",
}
)
func init() {
@ -74,14 +69,12 @@ func init() {
app.Action = statusd
app.HideVersion = true // separate command prints version
app.Commands = []cli.Command{
{
Action: version,
Name: "version",
Usage: "Print app version",
},
versionCommand,
wnodeCommand,
}
app.Flags = []cli.Flag{
NodeKeyFileFlag,
DataDirFlag,
NetworkIdFlag,
LightEthEnabledFlag,
@ -133,6 +126,7 @@ func makeNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) {
return nil, err
}
nodeConfig.NodeKeyFile = ctx.GlobalString(NodeKeyFileFlag.Name)
if !ctx.GlobalBool(HTTPEnabledFlag.Name) {
nodeConfig.HTTPHost = "" // HTTP RPC is disabled
}
@ -150,23 +144,6 @@ func makeNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) {
return nodeConfig, nil
}
// version displays app version
func version(ctx *cli.Context) error {
fmt.Println(strings.Title(params.DefaultClientIdentifier))
fmt.Println("Version:", params.Version)
if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit)
}
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())
return nil
}
// makeApp creates an app with sane defaults.
func makeApp(gitCommit string) *cli.App {
app := cli.NewApp()

36
cmd/statusd/misccmd.go Normal file
View File

@ -0,0 +1,36 @@
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: version,
Name: "version",
Usage: "Print app version",
}
)
// version displays app version
func version(ctx *cli.Context) error {
fmt.Println(strings.Title(params.ClientIdentifier))
fmt.Println("Version:", params.Version)
if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit)
}
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())
return nil
}

138
cmd/statusd/wnodecmd.go Normal file
View File

@ -0,0 +1,138 @@
package main
import (
"errors"
"fmt"
"path/filepath"
"github.com/status-im/status-go/geth"
"github.com/status-im/status-go/geth/params"
"gopkg.in/urfave/cli.v1"
)
var (
WhisperEchoModeFlag = cli.BoolTFlag{
Name: "echo",
Usage: "Echo mode, prints some arguments for diagnostics (default: true)",
}
WhisperBootstrapNodeFlag = cli.BoolTFlag{
Name: "bootstrap",
Usage: "Don't actively connect to peers, wait for incoming connections (default: true)",
}
WhisperNotificationServerNodeFlag = cli.BoolFlag{
Name: "notify",
Usage: "Node is capable of sending Push Notifications",
}
WhisperForwarderNodeFlag = cli.BoolFlag{
Name: "forward",
Usage: "Only forward messages, neither send nor decrypt messages",
}
WhisperMailserverNodeFlag = cli.BoolFlag{
Name: "mailserver",
Usage: "Delivers expired messages on demand",
}
WhisperPassword = cli.StringFlag{
Name: "password",
Usage: "Password, will be used for topic keys, as Mail & Notification Server password",
}
WhisperPortFlag = cli.IntFlag{
Name: "port",
Usage: "Whisper node's listening port",
Value: params.WhisperPort,
}
WhisperPoWFlag = cli.Float64Flag{
Name: "pow",
Usage: "PoW for messages to be added to queue, in float format",
Value: params.WhisperMinimumPoW,
}
WhisperTTLFlag = cli.IntFlag{
Name: "ttl",
Usage: "Time to live for messages, in seconds",
Value: params.WhisperTTL,
}
)
var (
wnodeCommand = cli.Command{
Action: wnode,
Name: "wnode",
Usage: "Starts Whisper/5 node",
Flags: []cli.Flag{
WhisperEchoModeFlag,
WhisperBootstrapNodeFlag,
WhisperNotificationServerNodeFlag,
WhisperForwarderNodeFlag,
WhisperMailserverNodeFlag,
WhisperPassword,
WhisperPoWFlag,
WhisperPortFlag,
WhisperTTLFlag,
},
}
)
// 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)
// inject test accounts
geth.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account1.pk")
geth.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account2.pk")
if err := geth.CreateAndRunNode(config); err != nil {
return err
}
// wait till node has been stopped
geth.NodeManagerInstance().Node().GethStack().Wait()
return nil
}
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
}
whisperConfig := nodeConfig.WhisperConfig
whisperConfig.Enabled = true
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.MailServerPassword = ctx.String(WhisperPassword.Name)
whisperConfig.NotificationServerPassword = ctx.String(WhisperPassword.Name) // the same for both mail and notification servers
whisperConfig.Port = ctx.Int(WhisperPortFlag.Name)
whisperConfig.TTL = ctx.Int(WhisperTTLFlag.Name)
whisperConfig.MinimumPoW = ctx.Float64(WhisperPoWFlag.Name)
if whisperConfig.MailServerNode && len(whisperConfig.MailServerPassword) == 0 {
return nil, errors.New("mail server requires --password to be specified")
}
if whisperConfig.NotificationServerNode && len(whisperConfig.NotificationServerPassword) == 0 {
return nil, errors.New("notification server requires --password to be specified")
}
return nodeConfig, nil
}

View File

@ -77,6 +77,39 @@ type LightEthConfig struct {
type WhisperConfig struct {
// Enabled flag specifies whether protocol is enabled
Enabled bool
// EchoMode if mode is on, prints some arguments for diagnostics
EchoMode bool
// BootstrapNode whether node doesn't actively connect to peers, and waits for incoming connections
BootstrapNode bool
// ForwarderNode is mode when node only forwards messages, neither sends nor decrypts messages
ForwarderNode bool
// MailServerNode is mode when node is capable of delivering expired messages on demand
MailServerNode bool
// MailServerPassword is password for MailServer's symmetric key
MailServerPassword string
// NotificationServerNode is mode when node is capable of sending Push (and probably other kinds) Notifications
NotificationServerNode bool
// NotificationServerPassword is password for NotificationServer's symmetric key (used in discovery)
NotificationServerPassword string
// DataDir is the file system folder Whisper should use for any data storage needs.
DataDir string
// Port Whisper node's listening port
Port int
// MinimumPoW minimum PoW for Whisper messages
MinimumPoW float64
// TTL time to live for messages, in seconds
TTL int
}
// SwarmConfig holds Swarm-related configuration
@ -96,6 +129,11 @@ type NodeConfig struct {
// DataDir is the file system folder the node should use for any data storage needs.
DataDir string
// PrivateKeyFile is a filename with node ID (private key)
// This file should contain a valid secp256k1 private key that will be used for both
// remote peer identification as well as network traffic encryption.
NodeKeyFile string
// Name sets the instance name of the node. It must not contain the / character.
Name string
@ -166,25 +204,29 @@ func NewNodeConfig(dataDir string, networkId int) (*NodeConfig, error) {
nodeConfig := &NodeConfig{
NetworkId: networkId,
DataDir: dataDir,
Name: DefaultClientIdentifier,
Name: ClientIdentifier,
Version: Version,
HTTPHost: DefaultHTTPHost,
HTTPPort: DefaultHTTPPort,
APIModules: DefaultAPIModules,
WSHost: DefaultWSHost,
WSPort: DefaultWSPort,
MaxPeers: DefaultMaxPeers,
MaxPendingPeers: DefaultMaxPendingPeers,
IPCFile: DefaultIPCFile,
LogFile: DefaultLogFile,
LogLevel: DefaultLogLevel,
HTTPHost: HTTPHost,
HTTPPort: HTTPPort,
APIModules: APIModules,
WSHost: WSHost,
WSPort: WSPort,
MaxPeers: MaxPeers,
MaxPendingPeers: MaxPendingPeers,
IPCFile: IPCFile,
LogFile: LogFile,
LogLevel: LogLevel,
ChainConfig: &ChainConfig{},
LightEthConfig: &LightEthConfig{
Enabled: true,
DatabaseCache: DefaultDatabaseCache,
DatabaseCache: DatabaseCache,
},
WhisperConfig: &WhisperConfig{
Enabled: true,
Enabled: true,
DataDir: filepath.Join(dataDir, "wnode"),
Port: WhisperPort,
MinimumPoW: WhisperMinimumPoW,
TTL: WhisperTTL,
},
SwarmConfig: &SwarmConfig{},
}
@ -293,3 +335,15 @@ func (c *NodeConfig) String() string {
data, _ := json.MarshalIndent(c, "", " ")
return string(data)
}
// String dumps config object as nicely indented JSON
func (c *WhisperConfig) String() string {
data, _ := json.MarshalIndent(c, "", " ")
return string(data)
}
// String dumps config object as nicely indented JSON
func (c *SwarmConfig) String() string {
data, _ := json.MarshalIndent(c, "", " ")
return string(data)
}

View File

@ -101,11 +101,11 @@ var loadConfigTestCases = []struct {
t.Fatal("wrong Name")
}
if nodeConfig.HTTPPort != params.DefaultHTTPPort {
if nodeConfig.HTTPPort != params.HTTPPort {
t.Fatal("wrong HTTPPort")
}
if nodeConfig.HTTPHost != params.DefaultHTTPHost {
if nodeConfig.HTTPHost != params.HTTPHost {
t.Fatal("wrong HTTPHost")
}

View File

@ -7,37 +7,37 @@ import (
)
const (
// DefaultClientIdentifier is client identifier to advertise over the network
DefaultClientIdentifier = "StatusIM"
// ClientIdentifier is client identifier to advertise over the network
ClientIdentifier = "StatusIM"
// DefaultDataDir is default data directory used by statusd executable
DefaultDataDir = "statusd-data"
// DataDir is default data directory used by statusd executable
DataDir = "statusd-data"
// DefaultIPCFile is filename of exposed IPC RPC Server
DefaultIPCFile = "geth.ipc"
// IPCFile is filename of exposed IPC RPC Server
IPCFile = "geth.ipc"
// DefaultHTTPHost is host interface for the HTTP RPC server
DefaultHTTPHost = "localhost"
// HTTPHost is host interface for the HTTP RPC server
HTTPHost = "localhost"
// DefaultHTTPPort is HTTP-RPC port (replaced in unit tests)
DefaultHTTPPort = 8545
// HTTPPort is HTTP-RPC port (replaced in unit tests)
HTTPPort = 8545
// DefaultAPIModules is a list of modules to expose vie HTTP RPC
// APIModules is a list of modules to expose vie HTTP RPC
// TODO remove "admin" on main net
DefaultAPIModules = "db,eth,net,web3,shh,personal,admin"
APIModules = "db,eth,net,web3,shh,personal,admin"
// DefaultWSHost is a host interface for the websocket RPC server
DefaultWSHost = "localhost"
// WSHost is a host interface for the websocket RPC server
WSHost = "localhost"
// DefaultWSPort is a WS-RPC port (replaced in unit tests)
DefaultWSPort = 8546
// WSPort is a WS-RPC port (replaced in unit tests)
WSPort = 8546
// DefaultMaxPeers is the maximum number of global peers
DefaultMaxPeers = 25
// MaxPeers is the maximum number of global peers
MaxPeers = 25
// DefaultMaxPendingPeers is the maximum number of peers that can be pending in the
// MaxPendingPeers is the maximum number of peers that can be pending in the
// handshake phase, counted separately for inbound and outbound connections.
DefaultMaxPendingPeers = 0
MaxPendingPeers = 0
// DefaultGas default amount of gas used for transactions
DefaultGas = 180000
@ -45,14 +45,23 @@ const (
// DefaultFileDescriptorLimit is fd limit that database can use
DefaultFileDescriptorLimit = uint64(2048)
// DefaultDatabaseCache is memory (in MBs) allocated to internal caching (min 16MB / database forced)
DefaultDatabaseCache = 128
// DatabaseCache is memory (in MBs) allocated to internal caching (min 16MB / database forced)
DatabaseCache = 128
// DefaultLogFile defines where to write logs to
DefaultLogFile = "geth.log"
// LogFile defines where to write logs to
LogFile = "geth.log"
// DefaultLogLevel defines the minimum log level to report
DefaultLogLevel = "INFO"
// LogLevel defines the minimum log level to report
LogLevel = "INFO"
// WhisperPort is Whisper node listening port
WhisperPort = 30379
// WhisperMinimumPoW amount of work for Whisper message to be added to sending queue
WhisperMinimumPoW = 0.001
// WhisperTTL is time to live for messages, in seconds
WhisperTTL = 120
// TestNetworkId is id of a test network
TestNetworkId = 3

View File

@ -2,6 +2,7 @@
"TestNet": true,
"NetworkId": 3,
"DataDir": "$TMPDIR",
"NodeKeyFile": "",
"Name": "StatusIM",
"Version": "$VERSION",
"APIModules": "db,eth,net,web3,shh,personal,admin",
@ -34,7 +35,18 @@
"DatabaseCache": 128
},
"WhisperConfig": {
"Enabled": true
"Enabled": true,
"EchoMode": false,
"BootstrapNode": false,
"ForwarderNode": false,
"MailServerNode": false,
"MailServerPassword": "",
"NotificationServerNode": false,
"NotificationServerPassword": "",
"DataDir": "$TMPDIR/wnode",
"Port": 30379,
"MinimumPoW": 0.001,
"TTL": 120
},
"SwarmConfig": {
"Enabled": false

View File

@ -168,23 +168,11 @@ func PrepareTestNode() (err error) {
return err
}
// import test account (with test ether on it)
importTestAccount := func(accountFile string) error {
dst := filepath.Join(TestDataDir, "keystore", accountFile)
if _, err := os.Stat(dst); os.IsNotExist(err) {
err = ioutil.WriteFile(dst, static.MustAsset("keys/"+accountFile), 0644)
if err != nil {
glog.V(logger.Warn).Infof("cannot copy test account PK: %v", err)
return err
}
}
return nil
}
if err := importTestAccount("test-account1.pk"); err != nil {
// import test accounts (with test ether on it)
if err := ImportTestAccount(filepath.Join(TestDataDir, "keystore"), "test-account1.pk"); err != nil {
panic(err)
}
if err := importTestAccount("test-account2.pk"); err != nil {
if err := ImportTestAccount(filepath.Join(TestDataDir, "keystore"), "test-account2.pk"); err != nil {
panic(err)
}
@ -286,3 +274,23 @@ func AddressToDecryptedAccount(address, password string) (accounts.Account, *key
return keyStore.AccountDecryptedKey(account, password)
}
// ImportTestAccount checks if test account exists in keystore, and if not
// tries to import it (from static resources, see "static/keys" folder)
func ImportTestAccount(keystoreDir, accountFile string) error {
// make sure that keystore folder exists
if _, err := os.Stat(keystoreDir); os.IsNotExist(err) {
os.MkdirAll(keystoreDir, os.ModePerm)
}
dst := filepath.Join(keystoreDir, accountFile)
if _, err := os.Stat(dst); os.IsNotExist(err) {
err = ioutil.WriteFile(dst, static.MustAsset("keys/"+accountFile), 0644)
if err != nil {
glog.V(logger.Warn).Infof("cannot copy test account PK: %v", err)
return err
}
}
return nil
}