diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index 3f09c0414e513ad752c503ad5fa0eda521761282..820e9d082a7315d559e27cb0da9f4bbff9678940 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 a275d8aa54dd3649b5d5e719c09ddac0172eac73..bcf9edbf0ecb7fe53f1cacd725840d7b55af0f4a 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 e4abf6b30c99f775c5a93ed3126053779c1a29d9..50c742e7c5743991b2e16df5f48d7eb240150c53 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 5c09e44ec40be6157971083b9cf1fcace5efc1ac..cb91f4539099dea4423499a43f9565f54cd051b9 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 <http://www.gnu.org/licenses/>.
 
+// 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(&ethServ)
+
+		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 0000000000000000000000000000000000000000..dfbf855e6252ea79e463fa584191368af66b2f39
--- /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 <http://www.gnu.org/licenses/>.
+
+// 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 774fa8eab693e4dfbc29e2e095d2129d7424dfcd..72666422734e42c71e80580ab5f9389f19188e03 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 738c0c548570ebc440423b77881fab4055619963..7ea4b2f650a4beec24a13012d773692eaf843f11 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 3070d10b53e51f39552824956d7b17491fb75f67..78ab56e3fb00e36c73427ed69e0e96ef65ca7787 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 <http://www.gnu.org/licenses/>.
 
-// 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
-}