From 4c8c5e2f7410d850ee6068b46c8b880f733743d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 25 Nov 2016 17:55:06 +0200 Subject: [PATCH] cmd, ethstats, les, mobile, params: native netstats (#3336) --- cmd/geth/consolecmd_test.go | 6 +- cmd/geth/main.go | 35 ++- cmd/geth/usage.go | 1 + cmd/utils/flags.go | 32 ++- ethstats/ethstats.go | 452 +++++++++++++++++++++++++++++++ les/backend.go | 1 + mobile/geth.go | 19 ++ {cmd/utils => params}/version.go | 33 +-- 8 files changed, 533 insertions(+), 46 deletions(-) create mode 100644 ethstats/ethstats.go rename {cmd/utils => params}/version.go (54%) diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 3f09c0414e..820e9d082a 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -28,7 +28,7 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -46,7 +46,7 @@ func TestConsoleWelcome(t *testing.T) { // Gather all the infos the welcome message needs to contain geth.setTemplateFunc("goos", func() string { return runtime.GOOS }) geth.setTemplateFunc("gover", runtime.Version) - geth.setTemplateFunc("gethver", func() string { return utils.Version }) + geth.setTemplateFunc("gethver", func() string { return params.Version }) geth.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) }) geth.setTemplateFunc("apis", func() []string { apis := append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi) @@ -132,7 +132,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint string) { // Gather all the infos the welcome message needs to contain attach.setTemplateFunc("goos", func() string { return runtime.GOOS }) attach.setTemplateFunc("gover", runtime.Version) - attach.setTemplateFunc("gethver", func() string { return utils.Version }) + attach.setTemplateFunc("gethver", func() string { return params.Version }) attach.setTemplateFunc("etherbase", func() string { return geth.Etherbase }) attach.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) }) attach.setTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") }) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a275d8aa54..bcf9edbf0e 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -40,6 +40,8 @@ import ( "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "gopkg.in/urfave/cli.v1" ) @@ -173,6 +175,7 @@ participating. utils.VMEnableJitFlag, utils.NetworkIdFlag, utils.RPCCORSDomainFlag, + utils.EthStatsURLFlag, utils.MetricsEnabledFlag, utils.FakePoWFlag, utils.SolcPathFlag, @@ -254,8 +257,24 @@ func initGenesis(ctx *cli.Context) error { } func makeFullNode(ctx *cli.Context) *node.Node { + // Create the default extradata and construct the base node + var clientInfo = struct { + Version uint + Name string + GoVersion string + Os string + }{uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch), clientIdentifier, runtime.Version(), runtime.GOOS} + extra, err := rlp.EncodeToBytes(clientInfo) + if err != nil { + glog.V(logger.Warn).Infoln("error setting canonical miner information:", err) + } + if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() { + glog.V(logger.Warn).Infoln("error setting canonical miner information: extra exceeds", params.MaximumExtraDataSize) + glog.V(logger.Debug).Infof("extra: %x\n", extra) + extra = nil + } stack := utils.MakeNode(ctx, clientIdentifier, gitCommit) - utils.RegisterEthService(ctx, stack, utils.MakeDefaultExtraData(clientIdentifier)) + utils.RegisterEthService(ctx, stack, extra) // Whisper must be explicitly enabled, but is auto-enabled in --dev mode. shhEnabled := ctx.GlobalBool(utils.WhisperEnabledFlag.Name) @@ -263,14 +282,17 @@ func makeFullNode(ctx *cli.Context) *node.Node { if shhEnabled || shhAutoEnabled { utils.RegisterShhService(stack) } - + // Add the Ethereum Stats daemon if requested + if url := ctx.GlobalString(utils.EthStatsURLFlag.Name); url != "" { + utils.RegisterEthStatsService(stack, url) + } // Add the release oracle service so it boots along with node. if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { config := release.Config{ Oracle: relOracle, - Major: uint32(utils.VersionMajor), - Minor: uint32(utils.VersionMinor), - Patch: uint32(utils.VersionPatch), + Major: uint32(params.VersionMajor), + Minor: uint32(params.VersionMinor), + Patch: uint32(params.VersionPatch), } commit, _ := hex.DecodeString(gitCommit) copy(config.Commit[:], commit) @@ -278,7 +300,6 @@ func makeFullNode(ctx *cli.Context) *node.Node { }); err != nil { utils.Fatalf("Failed to register the Geth release oracle service: %v", err) } - return stack } @@ -342,7 +363,7 @@ func makedag(ctx *cli.Context) error { func version(ctx *cli.Context) error { fmt.Println(strings.Title(clientIdentifier)) - fmt.Println("Version:", utils.Version) + fmt.Println("Version:", params.Version) if gitCommit != "" { fmt.Println("Git Commit:", gitCommit) } diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index e4abf6b30c..50c742e7c5 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -161,6 +161,7 @@ var AppHelpFlagGroups = []flagGroup{ { Name: "LOGGING AND DEBUGGING", Flags: append([]cli.Flag{ + utils.EthStatsURLFlag, utils.MetricsEnabledFlag, utils.FakePoWFlag, }, debug.Flags...), diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5c09e44ec4..cb91f45390 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . +// Package utils contains internal helper functions for go-ethereum commands. package utils import ( @@ -36,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/logger" @@ -86,7 +88,7 @@ func NewApp(gitCommit, usage string) *cli.App { app.Author = "" //app.Authors = nil app.Email = "" - app.Version = Version + app.Version = params.Version if gitCommit != "" { app.Version += "-" + gitCommit[:8] } @@ -242,8 +244,11 @@ var ( Name: "jitvm", Usage: "Enable the JIT VM", } - - // logging and debug settings + // Logging and debug settings + EthStatsURLFlag = cli.StringFlag{ + Name: "ethstats", + Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)", + } MetricsEnabledFlag = cli.BoolFlag{ Name: metrics.MetricsEnabledFlag, Usage: "Enable metrics collection and reporting", @@ -660,7 +665,7 @@ func MakePasswordList(ctx *cli.Context) []string { // MakeNode configures a node with no services from command line flags. func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node { - vsn := Version + vsn := params.Version if gitCommit != "" { vsn += "-" + gitCommit[:8] } @@ -801,13 +806,30 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) { } } -// RegisterShhService configures whisper and adds it to the given node. +// RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node) { if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { Fatalf("Failed to register the Whisper service: %v", err) } } +// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to +// th egiven node. +func RegisterEthStatsService(stack *node.Node, url string) { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + // Retrieve both eth and les services + var ethServ *eth.Ethereum + ctx.Service(ðServ) + + var lesServ *les.LightEthereum + ctx.Service(&lesServ) + + return ethstats.New(url, ethServ, lesServ) + }); err != nil { + Fatalf("Failed to register the Ethereum Stats service: %v", err) + } +} + // SetupNetwork configures the system for either the main net or some test network. func SetupNetwork(ctx *cli.Context) { switch { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go new file mode 100644 index 0000000000..dfbf855e62 --- /dev/null +++ b/ethstats/ethstats.go @@ -0,0 +1,452 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package ethstats implements the network stats reporting service. +package ethstats + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "regexp" + "runtime" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/net/websocket" +) + +// Service implements an Ethereum netstats reporting daemon that pushes local +// chain statistics up to a monitoring server. +type Service struct { + stack *node.Node // Temporary workaround, remove when API finalized + + server *p2p.Server // Peer-to-peer server to retrieve networking infos + eth *eth.Ethereum // Full Ethereum service if monitoring a full node + les *les.LightEthereum // Light Ethereum service if monitoring a light node + + node string // Name of the node to display on the monitoring page + pass string // Password to authorize access to the monitoring page + host string // Remote address of the monitoring service +} + +// New returns a monitoring service ready for stats reporting. +func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) { + // Parse the netstats connection url + re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") + parts := re.FindStringSubmatch(url) + if len(parts) != 5 { + return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) + } + // Assemble and return the stats service + return &Service{ + eth: ethServ, + les: lesServ, + node: parts[1], + pass: parts[3], + host: parts[4], + }, nil +} + +// Protocols implements node.Service, returning the P2P network protocols used +// by the stats service (nil as it doesn't use the devp2p overlay network). +func (s *Service) Protocols() []p2p.Protocol { return nil } + +// APIs implements node.Service, returning the RPC API endpoints provided by the +// stats service (nil as it doesn't provide any user callable APIs). +func (s *Service) APIs() []rpc.API { return nil } + +// Start implements node.Service, starting up the monitoring and reporting daemon. +func (s *Service) Start(server *p2p.Server) error { + s.server = server + go s.loop() + + glog.V(logger.Info).Infoln("Stats daemon started") + return nil +} + +// Stop implements node.Service, terminating the monitoring and reporting daemon. +func (s *Service) Stop() error { + glog.V(logger.Info).Infoln("Stats daemon stopped") + return nil +} + +// loop keeps trying to connect to the netstats server, reporting chain events +// until termination. +func (s *Service) loop() { + // Subscribe tso chain events to execute updates on + var emux *event.TypeMux + if s.eth != nil { + emux = s.eth.EventMux() + } else { + emux = s.les.EventMux() + } + headSub := emux.Subscribe(core.ChainHeadEvent{}) + defer headSub.Unsubscribe() + + txSub := emux.Subscribe(core.TxPreEvent{}) + defer txSub.Unsubscribe() + + // Loop reporting until termination + for { + // Establish a websocket connection to the server and authenticate the node + conn, err := websocket.Dial(fmt.Sprintf("wss://%s/api", s.host), "", "http://localhost/") + if err != nil { + glog.V(logger.Warn).Infof("Stats server unreachable: %v", err) + time.Sleep(10 * time.Second) + continue + } + in := json.NewDecoder(conn) + out := json.NewEncoder(conn) + + if err = s.login(in, out); err != nil { + glog.V(logger.Warn).Infof("Stats login failed: %v", err) + conn.Close() + time.Sleep(10 * time.Second) + continue + } + if err = s.report(in, out); err != nil { + glog.V(logger.Warn).Infof("Initial stats report failed: %v", err) + conn.Close() + continue + } + // Keep sending status updates until the connection breaks + fullReport := time.NewTicker(15 * time.Second) + + for err == nil { + select { + case <-fullReport.C: + if err = s.report(in, out); err != nil { + glog.V(logger.Warn).Infof("Full stats report failed: %v", err) + } + case <-headSub.Chan(): + // Exhaust events to avoid reporting too frequently + for exhausted := false; !exhausted; { + select { + case <-headSub.Chan(): + default: + exhausted = true + } + } + if err = s.reportBlock(out); err != nil { + glog.V(logger.Warn).Infof("Block stats report failed: %v", err) + } + case <-txSub.Chan(): + // Exhaust events to avoid reporting too frequently + for exhausted := false; !exhausted; { + select { + case <-headSub.Chan(): + default: + exhausted = true + } + } + if err = s.reportPending(out); err != nil { + glog.V(logger.Warn).Infof("Transaction stats report failed: %v", err) + } + } + } + // Make sure the connection is closed + conn.Close() + } +} + +// nodeInfo is the collection of metainformation about a node that is displayed +// on the monitoring page. +type nodeInfo struct { + Name string `json:"name"` + Node string `json:"node"` + Port int `json:"port"` + Network string `json:"net"` + Protocol string `json:"protocol"` + API string `json:"api"` + Os string `json:"os"` + OsVer string `json:"os_v"` + Client string `json:"client"` +} + +// authMsg is the authentication infos needed to login to a monitoring server. +type authMsg struct { + Id string `json:"id"` + Info nodeInfo `json:"info"` + Secret string `json:"secret"` +} + +// login tries to authorize the client at the remote server. +func (s *Service) login(in *json.Decoder, out *json.Encoder) error { + // Construct and send the login authentication + infos := s.server.NodeInfo() + + var network, protocol string + if info := infos.Protocols["eth"]; info != nil { + network = strconv.Itoa(info.(*eth.EthNodeInfo).Network) + protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) + } else { + network = strconv.Itoa(infos.Protocols["les"].(*eth.EthNodeInfo).Network) + protocol = fmt.Sprintf("les/%d", les.ProtocolVersions[0]) + } + auth := &authMsg{ + Id: s.node, + Info: nodeInfo{ + Name: s.node, + Node: infos.Name, + Port: infos.Ports.Listener, + Network: network, + Protocol: protocol, + API: "No", + Os: runtime.GOOS, + OsVer: runtime.GOARCH, + Client: "0.1.1", + }, + Secret: s.pass, + } + login := map[string][]interface{}{ + "emit": []interface{}{"hello", auth}, + } + if err := out.Encode(login); err != nil { + return err + } + // Retrieve the remote ack or connection termination + var ack map[string][]string + if err := in.Decode(&ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" { + return errors.New("unauthorized") + } + return nil +} + +// report collects all possible data to report and send it to the stats server. +// This should only be used on reconnects or rarely to avoid overloading the +// server. Use the individual methods for reporting subscribed events. +func (s *Service) report(in *json.Decoder, out *json.Encoder) error { + if err := s.reportLatency(in, out); err != nil { + return err + } + if err := s.reportBlock(out); err != nil { + return err + } + if err := s.reportPending(out); err != nil { + return err + } + if err := s.reportStats(out); err != nil { + return err + } + return nil +} + +// reportLatency sends a ping request to the server, measures the RTT time and +// finally sends a latency update. +func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error { + // Send the current time to the ethstats server + start := time.Now() + + ping := map[string][]interface{}{ + "emit": []interface{}{"node-ping", map[string]string{ + "id": s.node, + "clientTime": start.String(), + }}, + } + if err := out.Encode(ping); err != nil { + return err + } + // Wait for the pong request to arrive back + var pong map[string][]interface{} + if err := in.Decode(&pong); err != nil || len(pong["emit"]) != 2 || pong["emit"][0].(string) != "node-pong" { + return errors.New("unexpected ping reply") + } + // Send back the measured latency + latency := map[string][]interface{}{ + "emit": []interface{}{"latency", map[string]string{ + "id": s.node, + "latency": strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000)), + }}, + } + if err := out.Encode(latency); err != nil { + return err + } + return nil +} + +// blockStats is the information to report about individual blocks. +type blockStats struct { + Number *big.Int `json:"number"` + Hash common.Hash `json:"hash"` + Diff string `json:"difficulty"` + TotalDiff string `json:"totalDifficulty"` + Txs txStats `json:"transactions"` + Uncles uncleStats `json:"uncles"` +} + +// txStats is a custom wrapper around a transaction array to force serializing +// empty arrays instead of returning null for them. +type txStats []*types.Transaction + +func (s txStats) MarshalJSON() ([]byte, error) { + if txs := ([]*types.Transaction)(s); len(txs) > 0 { + return json.Marshal(txs) + } + return []byte("[]"), nil +} + +// uncleStats is a custom wrapper around an uncle array to force serializing +// empty arrays instead of returning null for them. +type uncleStats []*types.Header + +func (s uncleStats) MarshalJSON() ([]byte, error) { + if uncles := ([]*types.Header)(s); len(uncles) > 0 { + return json.Marshal(uncles) + } + return []byte("[]"), nil +} + +// reportBlock retrieves the current chain head and repors it to the stats server. +func (s *Service) reportBlock(out *json.Encoder) error { + // Gather the head block infos from the local blockchain + var ( + head *types.Header + td *big.Int + txs []*types.Transaction + uncles []*types.Header + ) + if s.eth != nil { + // Full nodes have all needed information available + block := s.eth.BlockChain().CurrentBlock() + + head = s.eth.BlockChain().CurrentHeader() + td = s.eth.BlockChain().GetTd(head.Hash(), head.Number.Uint64()) + + txs = block.Transactions() + uncles = block.Uncles() + } else { + // Light nodes would need on-demand lookups for transactions/uncles, skip + head = s.les.BlockChain().CurrentHeader() + td = s.les.BlockChain().GetTd(head.Hash(), head.Number.Uint64()) + } + // Assemble the block stats report and send it to the server + stats := map[string]interface{}{ + "id": s.node, + "block": &blockStats{ + Number: head.Number, + Hash: head.Hash(), + Diff: head.Difficulty.String(), + TotalDiff: td.String(), + Txs: txs, + Uncles: uncles, + }, + } + report := map[string][]interface{}{ + "emit": []interface{}{"block", stats}, + } + if err := out.Encode(report); err != nil { + return err + } + return nil +} + +// pendStats is the information to report about pending transactions. +type pendStats struct { + Pending int `json:"pending"` +} + +// reportPending retrieves the current number of pending transactions and reports +// it to the stats server. +func (s *Service) reportPending(out *json.Encoder) error { + // Retrieve the pending count from the local blockchain + var pending int + if s.eth != nil { + pending, _ = s.eth.TxPool().Stats() + } else { + pending = s.les.TxPool().Stats() + } + // Assemble the transaction stats and send it to the server + stats := map[string]interface{}{ + "id": s.node, + "stats": &pendStats{ + Pending: pending, + }, + } + report := map[string][]interface{}{ + "emit": []interface{}{"pending", stats}, + } + if err := out.Encode(report); err != nil { + return err + } + return nil +} + +// blockStats is the information to report about the local node. +type nodeStats struct { + Active bool `json:"active"` + Syncing bool `json:"syncing"` + Mining bool `json:"mining"` + Hashrate int `json:"hashrate"` + Peers int `json:"peers"` + GasPrice int `json:"gasPrice"` + Uptime int `json:"uptime"` +} + +// reportPending retrieves various stats about the node at the networking and +// mining layer and reports it to the stats server. +func (s *Service) reportStats(out *json.Encoder) error { + // Gather the syncing and mining infos from the local miner instance + var ( + mining bool + hashrate int + syncing bool + gasprice int + ) + if s.eth != nil { + mining = s.eth.Miner().Mining() + hashrate = int(s.eth.Miner().HashRate()) + + sync := s.eth.Downloader().Progress() + syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock + + gasprice = int(s.eth.Miner().GasPrice().Uint64()) + } else { + sync := s.les.Downloader().Progress() + syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock + } + stats := map[string]interface{}{ + "id": s.node, + "stats": &nodeStats{ + Active: true, + Mining: mining, + Hashrate: hashrate, + Peers: s.server.PeerCount(), + GasPrice: gasprice, + Syncing: syncing, + Uptime: 100, + }, + } + report := map[string][]interface{}{ + "emit": []interface{}{"stats", stats}, + } + if err := out.Encode(report); err != nil { + return err + } + return nil +} diff --git a/les/backend.go b/les/backend.go index 774fa8eab6..7266642273 100644 --- a/les/backend.go +++ b/les/backend.go @@ -180,6 +180,7 @@ func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchai func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool } func (s *LightEthereum) LesVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } func (s *LightEthereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux } // Protocols implements node.Service, returning all the currently configured // network protocols to start. diff --git a/mobile/geth.go b/mobile/geth.go index 738c0c5485..7ea4b2f650 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p/nat" @@ -65,6 +66,12 @@ type NodeConfig struct { // A minimum of 16MB is always reserved. EthereumDatabaseCache int + // EthereumNetStats is a netstats connection string to use to report various + // chain, transaction and node stats to a monitoring server. + // + // It has the form "nodename:secret@host:port" + EthereumNetStats string + // WhisperEnabled specifies whether the node should run the Whisper protocol. WhisperEnabled bool } @@ -106,6 +113,7 @@ func NewNode(datadir string, config *NodeConfig) (*Node, error) { // Create the empty networking stack nodeConf := &node.Config{ Name: clientIdentifier, + Version: params.Version, DataDir: datadir, KeyStoreDir: filepath.Join(datadir, "keystore"), // Mobile should never use internal keystores! NoDiscovery: true, @@ -150,6 +158,17 @@ func NewNode(datadir string, config *NodeConfig) (*Node, error) { }); err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } + // If netstats reporting is requested, do it + if config.EthereumNetStats != "" { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + var lesServ *les.LightEthereum + ctx.Service(&lesServ) + + return ethstats.New(config.EthereumNetStats, nil, lesServ) + }); err != nil { + return nil, fmt.Errorf("netstats init: %v", err) + } + } } // Register the Whisper protocol if requested if config.WhisperEnabled { diff --git a/cmd/utils/version.go b/params/version.go similarity index 54% rename from cmd/utils/version.go rename to params/version.go index 3070d10b53..78ab56e3fb 100644 --- a/cmd/utils/version.go +++ b/params/version.go @@ -14,18 +14,9 @@ // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . -// Package utils contains internal helper functions for go-ethereum commands. -package utils +package params -import ( - "fmt" - "runtime" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) +import "fmt" const ( VersionMajor = 1 // Major version component of the current release @@ -42,23 +33,3 @@ var Version = func() string { } return v }() - -// MakeDefaultExtraData returns the default Ethereum block extra data blob. -func MakeDefaultExtraData(clientIdentifier string) []byte { - var clientInfo = struct { - Version uint - Name string - GoVersion string - Os string - }{uint(VersionMajor<<16 | VersionMinor<<8 | VersionPatch), clientIdentifier, runtime.Version(), runtime.GOOS} - extra, err := rlp.EncodeToBytes(clientInfo) - if err != nil { - glog.V(logger.Warn).Infoln("error setting canonical miner information:", err) - } - if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() { - glog.V(logger.Warn).Infoln("error setting canonical miner information: extra exceeds", params.MaximumExtraDataSize) - glog.V(logger.Debug).Infof("extra: %x\n", extra) - return nil - } - return extra -}