Dmitry Shulyak 375d5ec8c3
Explicitly disable discovery (#886)
* Make it possible to explicitly disable discovery

Discovery will be disabled in following cases:
- if there are not bootnodes - v5 server will be disabled
because there is no point in running it
- if user defined in config NoDiscovery=true this value will be preserved
even if we have bootnodes

So, basically discovery will be always enabled by default on mobile, unless
it is explicitly specified otherwise.

When statusd is used current behavior is that discovery is disabled by default.
I kept it in this change, but it would be better to change it.

* Fix leftovers

* Add wait group to peer pool to protect from races with p2p.Server

* Change fields only when all goroutines finished

* Turn off discovery after topic searches are stopped

* Don't set period to nil to avoid race with SearchTopic

* Close period chan only when all writers are finished
2018-05-10 14:45:51 +03:00

377 lines
12 KiB
Go

package main
import (
"context"
"errors"
"flag"
"fmt"
stdlog "log"
"net/http"
"os"
"os/signal"
"runtime"
"strings"
"github.com/status-im/status-go/logutils"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/status-im/status-go/cmd/statusd/debug"
"github.com/status-im/status-go/cmd/statusd/topics"
"github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/metrics"
nodemetrics "github.com/status-im/status-go/metrics/node"
"github.com/status-im/status-go/profiling"
)
var (
gitCommit = "N/A" // rely on linker: -ldflags -X main.GitCommit"
buildStamp = "N/A" // rely on linker: -ldflags -X main.buildStamp"
)
var (
clusterConfigFile = flag.String("clusterconfig", "", "Cluster configuration file")
nodeKeyFile = flag.String("nodekey", "", "P2P node key file (private key)")
dataDir = flag.String("datadir", params.DataDir, "Data directory for the databases and keystore")
networkID = flag.Int("networkid", params.RopstenNetworkID, "Network identifier (integer, 1=Homestead, 3=Ropsten, 4=Rinkeby, 777=StatusChain)")
lesEnabled = flag.Bool("les", false, "Enable LES protocol")
whisperEnabled = flag.Bool("shh", false, "Enable Whisper protocol")
statusService = flag.String("status", "", `Enable StatusService, possible values: "ipc", "http"`)
swarmEnabled = flag.Bool("swarm", false, "Enable Swarm protocol")
maxPeers = flag.Int("maxpeers", 25, "maximum number of p2p peers (including all protocols)")
httpEnabled = flag.Bool("http", false, "Enable HTTP RPC endpoint")
httpHost = flag.String("httphost", "127.0.0.1", "HTTP RPC host of the listening socket")
httpPort = flag.Int("httpport", params.HTTPPort, "HTTP RPC server's listening port")
ipcEnabled = flag.Bool("ipc", false, "Enable IPC RPC endpoint")
ipcFile = flag.String("ipcfile", "", "Set IPC file path")
cliEnabled = flag.Bool("cli", false, "Enable debugging CLI server")
cliPort = flag.String("cliport", debug.CLIPort, "CLI server's listening port")
pprofEnabled = flag.Bool("pprof", false, "Enable runtime profiling via pprof")
pprofPort = flag.Int("pprofport", 52525, "Port for runtime profiling via pprof")
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")
listenAddr = flag.String("listenaddr", ":30303", "IP address and port of this node (e.g. 127.0.0.1:30303)")
standalone = flag.Bool("standalone", true, "Don't actively connect to peers, wait for incoming connections")
bootnodes = flag.String("bootnodes", "", "A list of bootnodes separated by comma")
discovery = flag.Bool("discovery", false, "Enable discovery protocol")
// stats
statsEnabled = flag.Bool("stats", false, "Expose node stats via /debug/vars expvar endpoint or Prometheus")
statsAddr = flag.String("stats.addr", "0.0.0.0:8080", "HTTP address with /debug/vars endpoint")
// don't change the name of this flag, https://github.com/ethereum/go-ethereum/blob/master/metrics/metrics.go#L41
_ = flag.Bool("metrics", false, "Expose ethereum metrics with debug_metrics jsonrpc call.")
// shh stuff
passwordFile = flag.String("shh.passwordfile", "", "Password file (password is used for symmetric encryption)")
minPow = flag.Float64("shh.pow", params.WhisperMinimumPoW, "PoW for messages to be added to queue, in float format")
ttl = flag.Int("shh.ttl", params.WhisperTTL, "Time to live for messages, in seconds")
lightClient = flag.Bool("shh.lightclient", false, "Start with empty bloom filter, and don't forward messages")
// MailServer
enableMailServer = flag.Bool("shh.mailserver", false, "Delivers expired messages on demand")
// Push Notification
firebaseAuth = flag.String("shh.firebaseauth", "", "FCM Authorization Key used for sending Push Notifications")
syncAndExit = flag.Int("sync-and-exit", -1, "Timeout in minutes for blockchain sync and exit, zero means no timeout unless sync is finished")
// Topics that will be search and registered by discovery v5.
searchTopics = topics.TopicLimitsFlag{}
registerTopics = topics.TopicFlag{}
)
// All general log messages in this package should be routed through this logger.
var logger = log.New("package", "status-go/cmd/statusd")
func main() {
flag.Var(&searchTopics, "topic.search", "Topic that will be searched in discovery v5, e.g (mailserver=1,1)")
flag.Var(&registerTopics, "topic.register", "Topic that will be registered using discovery v5.")
flag.Usage = printUsage
flag.Parse()
config, err := makeNodeConfig()
if err != nil {
stdlog.Fatalf("Making config failed, %s", err)
}
if *version {
printVersion(config, gitCommit, buildStamp)
return
}
if err := logutils.OverrideRootLog(config.LogEnabled, config.LogLevel, config.LogFile, true); err != nil {
stdlog.Fatalf("Error initializing logger: %s", err)
}
backend := api.NewStatusBackend()
err = backend.StartNode(config)
if err != nil {
logger.Error("Node start failed", "error", err)
return
}
// handle interrupt signals
interruptCh := haltOnInterruptSignal(backend.StatusNode())
// Check if debugging CLI connection shall be enabled.
if *cliEnabled {
err := startDebug(backend)
if err != nil {
logger.Error("Starting debugging CLI server failed", "error", err)
return
}
}
// Check if profiling shall be enabled.
if *pprofEnabled {
profiling.NewProfiler(*pprofPort).Go()
}
// Run stats server.
if *statsEnabled {
go startCollectingStats(interruptCh, backend.StatusNode())
}
// Sync blockchain and stop.
if *syncAndExit >= 0 {
exitCode := syncAndStopNode(interruptCh, backend.StatusNode(), *syncAndExit)
// Call was interrupted. Wait for graceful shutdown.
if exitCode == -1 {
if node := backend.StatusNode().GethNode(); node != nil {
node.Wait()
}
return
}
// Otherwise, exit immediately with a returned exit code.
os.Exit(exitCode)
}
node := backend.StatusNode().GethNode()
if node != nil {
// wait till node has been stopped
node.Wait()
}
}
// startDebug starts the debugging API server.
func startDebug(backend *api.StatusBackend) error {
statusAPI := api.NewStatusAPIWithBackend(backend)
_, err := debug.New(statusAPI, *cliPort)
return err
}
// startCollectingStats collects various stats about the node and other protocols like Whisper.
func startCollectingStats(interruptCh <-chan struct{}, statusNode *node.StatusNode) {
logger.Info("Starting stats", "stats", *statsAddr)
node := statusNode.GethNode()
if node == nil {
logger.Error("Failed to run metrics because it could not get the node")
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
if err := nodemetrics.SubscribeServerEvents(ctx, node); err != nil {
logger.Error("Failed to subscribe server events", "error", err)
}
}()
server := metrics.NewMetricsServer(*statsAddr)
defer func() {
// server may be nil if `-stats` flag is used
// but the binary is compiled without metrics enabled
if server == nil {
return
}
if err := server.Shutdown(context.TODO()); err != nil {
logger.Error("Failed to shutdown metrics server", "error", err)
}
}()
go func() {
// server may be nil if `-stats` flag is used
// but the binary is compiled without metrics enabled
if server == nil {
return
}
err := server.ListenAndServe()
switch err {
case http.ErrServerClosed:
default:
logger.Error("Metrics server failed", "error", err)
}
}()
<-interruptCh
}
// makeNodeConfig parses incoming CLI options and returns node configuration object
func makeNodeConfig() (*params.NodeConfig, error) {
nodeConfig, err := params.NewNodeConfig(*dataDir, *clusterConfigFile, uint64(*networkID))
if err != nil {
return nil, err
}
nodeConfig.ListenAddr = *listenAddr
// TODO(divan): move this logic into params package
if *nodeKeyFile != "" {
nodeConfig.NodeKeyFile = *nodeKeyFile
}
if *logLevel != "" {
nodeConfig.LogLevel = *logLevel
}
if *logFile != "" {
nodeConfig.LogFile = *logFile
}
if *logLevel != "" || *logFile != "" {
nodeConfig.LogEnabled = true
}
nodeConfig.RPCEnabled = *httpEnabled
nodeConfig.WhisperConfig.Enabled = *whisperEnabled
nodeConfig.MaxPeers = *maxPeers
nodeConfig.HTTPHost = *httpHost
nodeConfig.HTTPPort = *httpPort
nodeConfig.IPCEnabled = *ipcEnabled
if *ipcFile != "" {
nodeConfig.IPCEnabled = true
nodeConfig.IPCFile = *ipcFile
}
nodeConfig.LightEthConfig.Enabled = *lesEnabled
nodeConfig.SwarmConfig.Enabled = *swarmEnabled
if *standalone {
nodeConfig.ClusterConfig.Enabled = false
nodeConfig.ClusterConfig.BootNodes = nil
}
nodeConfig.NoDiscovery = !(*discovery)
nodeConfig.RequireTopics = map[discv5.Topic]params.Limits(searchTopics)
nodeConfig.RegisterTopics = []discv5.Topic(registerTopics)
// Even if standalone is true and discovery is disabled,
// it's possible to use bootnodes.
if *bootnodes != "" {
nodeConfig.ClusterConfig.BootNodes = strings.Split(*bootnodes, ",")
}
nodeConfig, err = configureStatusService(*statusService, nodeConfig)
if err != nil {
return nil, err
}
if *whisperEnabled {
return whisperConfig(nodeConfig)
}
// RPC configuration
if !*httpEnabled {
nodeConfig.HTTPHost = "" // HTTP RPC is disabled
}
return nodeConfig, nil
}
var errStatusServiceRequiresIPC = errors.New("to enable the StatusService on IPC, -ipc flag must be set")
var errStatusServiceRequiresHTTP = errors.New("to enable the StatusService on HTTP, -http flag must be set")
var errStatusServiceInvalidFlag = errors.New("-status flag valid values are: ipc, http")
func configureStatusService(flagValue string, nodeConfig *params.NodeConfig) (*params.NodeConfig, error) {
switch flagValue {
case "ipc":
if !nodeConfig.IPCEnabled {
return nil, errStatusServiceRequiresIPC
}
nodeConfig.StatusServiceEnabled = true
case "http":
if !nodeConfig.RPCEnabled {
return nil, errStatusServiceRequiresHTTP
}
nodeConfig.StatusServiceEnabled = true
nodeConfig.AddAPIModule("status")
case "":
nodeConfig.StatusServiceEnabled = false
default:
return nil, errStatusServiceInvalidFlag
}
return nodeConfig, nil
}
// printVersion prints verbose output about version and config.
func printVersion(config *params.NodeConfig, gitCommit, buildStamp string) {
if gitCommit != "" && len(gitCommit) > 8 {
params.Version += "-" + gitCommit[:8]
}
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() {
usage := `
Usage: statusd [options]
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"
statusd -cli # enable connection by statusd-cli on default port
Options:
`
fmt.Fprintf(os.Stderr, usage)
flag.PrintDefaults()
}
// haltOnInterruptSignal catches interrupt signal (SIGINT) and
// stops the node. It times out after 5 seconds
// if the node can not be stopped.
func haltOnInterruptSignal(statusNode *node.StatusNode) <-chan struct{} {
interruptCh := make(chan struct{})
go func() {
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt)
defer signal.Stop(signalCh)
<-signalCh
close(interruptCh)
logger.Info("Got interrupt, shutting down...")
if err := statusNode.Stop(); err != nil {
logger.Error("Failed to stop node", "error", err)
os.Exit(1)
}
}()
return interruptCh
}