whisper: `statusd wnode` sub-command, closes #140
This commit is contained in:
parent
21344af199
commit
9a68fd211f
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue