github.com/gochain-io/gochain@v2.2.26+incompatible/netstats/netstats.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package netstats implements the network stats reporting service.
    18  package netstats
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"math/big"
    26  	"net"
    27  	"regexp"
    28  	"runtime"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	"go.opencensus.io/trace"
    34  
    35  	"github.com/gochain-io/gochain/common"
    36  	"github.com/gochain-io/gochain/common/mclock"
    37  	"github.com/gochain-io/gochain/consensus"
    38  	"github.com/gochain-io/gochain/core"
    39  	"github.com/gochain-io/gochain/core/types"
    40  	"github.com/gochain-io/gochain/eth"
    41  	"github.com/gochain-io/gochain/les"
    42  	"github.com/gochain-io/gochain/log"
    43  	"github.com/gochain-io/gochain/p2p"
    44  	"github.com/gochain-io/gochain/rpc"
    45  	"golang.org/x/net/websocket"
    46  )
    47  
    48  const (
    49  	// historyUpdateRange is the number of blocks a node should report upon login or
    50  	// history request.
    51  	historyUpdateRange = 50
    52  )
    53  
    54  type txPool interface {
    55  	SubscribeNewTxsEvent(chan<- core.NewTxsEvent, string)
    56  	UnsubscribeNewTxsEvent(chan<- core.NewTxsEvent)
    57  }
    58  
    59  type blockChain interface {
    60  	SubscribeChainHeadEvent(chan<- core.ChainHeadEvent, string)
    61  	UnsubscribeChainHeadEvent(chan<- core.ChainHeadEvent)
    62  }
    63  
    64  // Service implements an GoChain netstats reporting daemon that pushes local
    65  // chain statistics up to a monitoring server.
    66  type Service struct {
    67  	server *p2p.Server       // Peer-to-peer server to retrieve networking infos
    68  	eth    *eth.GoChain      // Full GoChain service if monitoring a full node
    69  	les    *les.LightGoChain // Light GoChain service if monitoring a light node
    70  	engine consensus.Engine  // Consensus engine to retrieve variadic block fields
    71  
    72  	node string // Name of the node to display on the monitoring page
    73  	pass string // Password to authorize access to the monitoring page
    74  	host string // Remote address of the monitoring service
    75  
    76  	pongCh chan struct{} // Pong notifications are fed into this channel
    77  	histCh chan []uint64 // History request block numbers are fed into this channel
    78  }
    79  
    80  type Config struct {
    81  	Name   string `toml:",omitempty"`
    82  	Secret string `toml:",omitempty"`
    83  	URL    string `toml:",omitempty"`
    84  }
    85  
    86  // ParseConfig parses the netstats flag. It has the form "nodename:secret@host:port".
    87  func ParseConfig(flag string) (Config, error) {
    88  	re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)")
    89  	parts := re.FindStringSubmatch(flag)
    90  	if len(parts) != 5 {
    91  		return Config{}, fmt.Errorf("invalid netstats flag: \"%s\", should be nodename:secret@host:port", flag)
    92  	}
    93  	return Config{
    94  		Name:   parts[1],
    95  		Secret: parts[3],
    96  		URL:    parts[4],
    97  	}, nil
    98  }
    99  
   100  // New returns a monitoring service ready for stats reporting.
   101  func New(cfg Config, ethServ *eth.GoChain, lesServ *les.LightGoChain) *Service {
   102  	// Assemble and return the stats service
   103  	var engine consensus.Engine
   104  	if ethServ != nil {
   105  		engine = ethServ.Engine()
   106  	} else {
   107  		engine = lesServ.Engine()
   108  	}
   109  	return &Service{
   110  		eth:    ethServ,
   111  		les:    lesServ,
   112  		engine: engine,
   113  		node:   cfg.Name,
   114  		pass:   cfg.Secret,
   115  		host:   cfg.URL,
   116  		pongCh: make(chan struct{}),
   117  		histCh: make(chan []uint64, 1),
   118  	}
   119  }
   120  
   121  // Protocols implements node.Service, returning the P2P network protocols used
   122  // by the stats service (nil as it doesn't use the devp2p overlay network).
   123  func (s *Service) Protocols() []p2p.Protocol { return nil }
   124  
   125  // APIs implements node.Service, returning the RPC API endpoints provided by the
   126  // stats service (nil as it doesn't provide any user callable APIs).
   127  func (s *Service) APIs() []rpc.API { return nil }
   128  
   129  // Start implements node.Service, starting up the monitoring and reporting daemon.
   130  func (s *Service) Start(server *p2p.Server) error {
   131  	s.server = server
   132  	go s.loop()
   133  
   134  	log.Info("Stats daemon started")
   135  	return nil
   136  }
   137  
   138  // Stop implements node.Service, terminating the monitoring and reporting daemon.
   139  func (s *Service) Stop() error {
   140  	log.Info("Stats daemon stopped")
   141  	return nil
   142  }
   143  
   144  // loop keeps trying to connect to the netstats server, reporting chain events
   145  // until termination.
   146  func (s *Service) loop() {
   147  	// Subscribe to chain events to execute updates on
   148  	var blockchain blockChain
   149  	var txpool txPool
   150  	if s.eth != nil {
   151  		blockchain = s.eth.BlockChain()
   152  		txpool = s.eth.TxPool()
   153  	} else {
   154  		blockchain = s.les.BlockChain()
   155  		txpool = s.les.TxPool()
   156  	}
   157  
   158  	// Start a goroutine that exhausts the subscriptions to avoid events piling up
   159  	var (
   160  		chainHeadCh = make(chan core.ChainHeadEvent, 1)
   161  		txEventCh   = make(chan core.NewTxsEvent, 1)
   162  		quitCh      = make(chan struct{})
   163  		headCh      = make(chan *types.Block, 1)
   164  		txCh        = make(chan struct{}, 1)
   165  	)
   166  	blockchain.SubscribeChainHeadEvent(chainHeadCh, "netstats.Service")
   167  	txpool.SubscribeNewTxsEvent(txEventCh, "netstats.Service")
   168  	go func() {
   169  		defer close(quitCh)
   170  		defer blockchain.UnsubscribeChainHeadEvent(chainHeadCh)
   171  		defer txpool.UnsubscribeNewTxsEvent(txEventCh)
   172  
   173  		var lastTx mclock.AbsTime
   174  		for {
   175  			select {
   176  			// Notify of chain head events.
   177  			case head, ok := <-chainHeadCh:
   178  				if !ok {
   179  					return
   180  				}
   181  				select {
   182  				case headCh <- head.Block:
   183  				default:
   184  				}
   185  
   186  			// Notify of new transaction events, but drop if too frequent
   187  			case _, ok := <-txEventCh:
   188  				if !ok {
   189  					return
   190  				}
   191  				if time.Duration(mclock.Now()-lastTx) < time.Second {
   192  					continue
   193  				}
   194  				lastTx = mclock.Now()
   195  
   196  				select {
   197  				case txCh <- struct{}{}:
   198  				default:
   199  				}
   200  			}
   201  		}
   202  	}()
   203  	// Resolve the URL, defaulting to TLS, but falling back to none too
   204  	path := fmt.Sprintf("%s/api", s.host)
   205  	urls := []string{path}
   206  
   207  	if !strings.Contains(path, "://") { // url.Parse and url.IsAbs is unsuitable (https://github.com/golang/go/issues/19779)
   208  		urls = []string{"wss://" + path, "ws://" + path}
   209  	}
   210  	// Loop reporting until termination
   211  	for {
   212  		conn := s.connect(urls)
   213  		if conn == nil {
   214  			select {
   215  			case <-quitCh:
   216  				return
   217  			case <-time.After(10 * time.Second):
   218  			}
   219  			continue
   220  		}
   221  		// Respond to specific requests.
   222  		go s.readLoop(conn)
   223  		// Scheduled and triggered reports.
   224  		s.reportLoop(conn, quitCh, txCh, headCh)
   225  		select {
   226  		case <-quitCh:
   227  			return
   228  		default:
   229  		}
   230  	}
   231  }
   232  
   233  // connect establishes a websocket connection to the server on any supported URL, or returns nil if unable.
   234  func (s *Service) connect(urls []string) *websocket.Conn {
   235  	var (
   236  		conf *websocket.Config
   237  		conn *websocket.Conn
   238  		err  error
   239  	)
   240  	for _, url := range urls {
   241  		if conf, err = websocket.NewConfig(url, "http://localhost/"); err != nil {
   242  			continue
   243  		}
   244  		conf.Dialer = &net.Dialer{Timeout: 5 * time.Second}
   245  		if conn, err = websocket.DialConfig(conf); err == nil {
   246  			break
   247  		}
   248  	}
   249  	if err != nil {
   250  		log.Warn("Stats server unreachable", "err", err)
   251  		return nil
   252  	}
   253  	// Authenticate the client with the server
   254  	if err = s.login(conn); err != nil {
   255  		log.Warn("Stats login failed", "err", err)
   256  		conn.Close()
   257  		return nil
   258  	}
   259  	return conn
   260  }
   261  
   262  // readLoop loops as long as the connection is alive and retrieves data packets
   263  // from the network socket. If any of them match an active request, it forwards
   264  // it, if they themselves are requests it initiates a reply, and lastly it drops
   265  // unknown packets.
   266  func (s *Service) readLoop(conn *websocket.Conn) {
   267  	// If the read loop exists, close the connection
   268  	defer conn.Close()
   269  
   270  	for {
   271  		// Retrieve the next generic network packet and bail out on error
   272  		var msg map[string][]interface{}
   273  		if err := websocket.JSON.Receive(conn, &msg); err != nil {
   274  			log.Warn("Failed to decode stats server message", "err", err)
   275  			return
   276  		}
   277  		log.Trace("Received message from stats server", "msg", msg)
   278  		if len(msg["emit"]) == 0 {
   279  			log.Warn("Stats server sent non-broadcast", "msg", msg)
   280  			return
   281  		}
   282  		command, ok := msg["emit"][0].(string)
   283  		if !ok {
   284  			log.Warn("Invalid stats server message type", "type", msg["emit"][0])
   285  			return
   286  		}
   287  		// If the message is a ping reply, deliver (someone must be listening!)
   288  		if len(msg["emit"]) == 2 && command == "node-pong" {
   289  			select {
   290  			case s.pongCh <- struct{}{}:
   291  				// Pong delivered, continue listening
   292  				continue
   293  			default:
   294  				// Ping routine dead, abort
   295  				log.Warn("Stats server pinger seems to have died")
   296  				return
   297  			}
   298  		}
   299  		// If the message is a history request, forward to the event processor
   300  		if len(msg["emit"]) == 2 && command == "history" {
   301  			// Make sure the request is valid and doesn't crash us
   302  			request, ok := msg["emit"][1].(map[string]interface{})
   303  			if !ok {
   304  				log.Warn("Invalid stats history request", "msg", msg["emit"][1])
   305  				s.histCh <- nil
   306  				continue // Netstats sometime sends invalid history requests, ignore those
   307  			}
   308  			list, ok := request["list"].([]interface{})
   309  			if !ok {
   310  				log.Warn("Invalid stats history block list", "list", request["list"])
   311  				return
   312  			}
   313  			// Convert the block number list to an integer list
   314  			numbers := make([]uint64, len(list))
   315  			for i, num := range list {
   316  				n, ok := num.(float64)
   317  				if !ok {
   318  					log.Warn("Invalid stats history block number", "number", num)
   319  					return
   320  				}
   321  				numbers[i] = uint64(n)
   322  			}
   323  			select {
   324  			case s.histCh <- numbers:
   325  				continue
   326  			default:
   327  			}
   328  		}
   329  		// Report anything else and continue
   330  		log.Info("Unknown stats message", "msg", msg)
   331  	}
   332  }
   333  
   334  // reportLoop sends status updates until the connection breaks.
   335  func (s *Service) reportLoop(conn *websocket.Conn, quitCh, txCh <-chan struct{}, headCh <-chan *types.Block) {
   336  	defer conn.Close()
   337  
   338  	// Send the initial stats so our node looks decent from the get go
   339  	if err := s.report(context.Background(), conn); err != nil {
   340  		log.Warn("Initial stats report failed", "err", err)
   341  		return
   342  	}
   343  
   344  	fullReport := time.NewTicker(5 * time.Second)
   345  	defer fullReport.Stop()
   346  
   347  	var err error
   348  	for err == nil {
   349  		select {
   350  		case <-quitCh:
   351  			return
   352  
   353  		case <-fullReport.C:
   354  			ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-fullReport")
   355  			if err = s.report(ctx, conn); err != nil {
   356  				log.Warn("Full stats report failed", "err", err)
   357  			}
   358  			span.End()
   359  		case list := <-s.histCh:
   360  			ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-histCh")
   361  			if err = s.reportHistory(ctx, conn, list); err != nil {
   362  				log.Warn("Requested history report failed", "err", err)
   363  			}
   364  			span.End()
   365  		case head := <-headCh:
   366  			ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-headCh")
   367  			if err = s.reportBlock(ctx, conn, head); err != nil {
   368  				log.Warn("Block stats report failed", "err", err)
   369  			}
   370  			if err = s.reportPending(ctx, conn); err != nil {
   371  				log.Warn("Post-block transaction stats report failed", "err", err)
   372  			}
   373  			span.End()
   374  		case <-txCh:
   375  			ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-txCh")
   376  			if err = s.reportPending(ctx, conn); err != nil {
   377  				log.Warn("Transaction stats report failed", "err", err)
   378  			}
   379  			span.End()
   380  		}
   381  	}
   382  }
   383  
   384  // nodeInfo is the collection of metainformation about a node that is displayed
   385  // on the monitoring page.
   386  type nodeInfo struct {
   387  	Name     string `json:"name"`
   388  	Node     string `json:"node"`
   389  	Port     int    `json:"port"`
   390  	Network  string `json:"net"`
   391  	Protocol string `json:"protocol"`
   392  	API      string `json:"api"`
   393  	Os       string `json:"os"`
   394  	OsVer    string `json:"os_v"`
   395  	Client   string `json:"client"`
   396  	History  bool   `json:"canUpdateHistory"`
   397  }
   398  
   399  // authMsg is the authentication infos needed to login to a monitoring server.
   400  type authMsg struct {
   401  	Id     string   `json:"id"`
   402  	Info   nodeInfo `json:"info"`
   403  	Secret string   `json:"secret"`
   404  }
   405  
   406  // login tries to authorize the client at the remote server.
   407  func (s *Service) login(conn *websocket.Conn) error {
   408  	// Construct and send the login authentication
   409  	infos := s.server.NodeInfo()
   410  
   411  	var network, protocol string
   412  	if info := infos.Protocols["eth"]; info != nil {
   413  		network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network)
   414  		protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
   415  	} else {
   416  		network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network)
   417  		protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0])
   418  	}
   419  	auth := &authMsg{
   420  		Id: s.node,
   421  		Info: nodeInfo{
   422  			Name:     s.node,
   423  			Node:     infos.Name,
   424  			Port:     infos.Ports.Listener,
   425  			Network:  network,
   426  			Protocol: protocol,
   427  			API:      "No",
   428  			Os:       runtime.GOOS,
   429  			OsVer:    runtime.GOARCH,
   430  			Client:   "0.1.1",
   431  			History:  true,
   432  		},
   433  		Secret: s.pass,
   434  	}
   435  	login := map[string][]interface{}{
   436  		"emit": {"hello", auth},
   437  	}
   438  	if err := websocket.JSON.Send(conn, login); err != nil {
   439  		return err
   440  	}
   441  	// Retrieve the remote ack or connection termination
   442  	var ack map[string][]string
   443  	if err := websocket.JSON.Receive(conn, &ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" {
   444  		return errors.New("unauthorized")
   445  	}
   446  	return nil
   447  }
   448  
   449  // report collects all possible data to report and send it to the stats server.
   450  // This should only be used on reconnects or rarely to avoid overloading the
   451  // server. Use the individual methods for reporting subscribed events.
   452  func (s *Service) report(ctx context.Context, conn *websocket.Conn) error {
   453  	ctx, span := trace.StartSpan(ctx, "Service.report")
   454  	defer span.End()
   455  
   456  	if err := s.reportLatency(ctx, conn); err != nil {
   457  		return err
   458  	}
   459  	if err := s.reportBlock(ctx, conn, nil); err != nil {
   460  		return err
   461  	}
   462  	if err := s.reportPending(ctx, conn); err != nil {
   463  		return err
   464  	}
   465  	if err := s.reportStats(ctx, conn); err != nil {
   466  		return err
   467  	}
   468  	return nil
   469  }
   470  
   471  // reportLatency sends a ping request to the server, measures the RTT time and
   472  // finally sends a latency update.
   473  func (s *Service) reportLatency(ctx context.Context, conn *websocket.Conn) error {
   474  	ctx, span := trace.StartSpan(ctx, "Service.reportLatency")
   475  	defer span.End()
   476  
   477  	// Send the current time to the netstats server
   478  	start := time.Now()
   479  
   480  	ping := map[string][]interface{}{
   481  		"emit": {"node-ping", map[string]string{
   482  			"id":         s.node,
   483  			"clientTime": start.String(),
   484  		}},
   485  	}
   486  	if err := websocket.JSON.Send(conn, ping); err != nil {
   487  		return err
   488  	}
   489  	// Wait for the pong request to arrive back
   490  	select {
   491  	case <-s.pongCh:
   492  		// Pong delivered, report the latency
   493  	case <-time.After(5 * time.Second):
   494  		// Ping timeout, abort
   495  		return errors.New("ping timed out")
   496  	}
   497  	latency := strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000))
   498  
   499  	// Send back the measured latency
   500  	log.Trace("Sending measured latency to netstats", "latency", latency)
   501  
   502  	stats := map[string][]interface{}{
   503  		"emit": {"latency", map[string]string{
   504  			"id":      s.node,
   505  			"latency": latency,
   506  		}},
   507  	}
   508  	return websocket.JSON.Send(conn, stats)
   509  }
   510  
   511  // blockStats is the information to report about individual blocks.
   512  type blockStats struct {
   513  	Number     *big.Int       `json:"number"`
   514  	Hash       common.Hash    `json:"hash"`
   515  	ParentHash common.Hash    `json:"parentHash"`
   516  	Timestamp  *big.Int       `json:"timestamp"`
   517  	Miner      common.Address `json:"miner"`
   518  	GasUsed    uint64         `json:"gasUsed"`
   519  	GasLimit   uint64         `json:"gasLimit"`
   520  	Diff       string         `json:"difficulty"`
   521  	TotalDiff  string         `json:"totalDifficulty"`
   522  	Txs        []txStats      `json:"transactions"`
   523  	TxCount    uint64         `json:"transactionCount"`
   524  	TxHash     common.Hash    `json:"transactionsRoot"`
   525  	Root       common.Hash    `json:"stateRoot"`
   526  	Uncles     uncleStats     `json:"uncles"`
   527  }
   528  
   529  // txStats is the information to report about individual transactions.
   530  type txStats struct {
   531  	Hash common.Hash `json:"hash"`
   532  }
   533  
   534  // uncleStats is a custom wrapper around an uncle array to force serializing
   535  // empty arrays instead of returning null for them.
   536  type uncleStats []*types.Header
   537  
   538  func (s uncleStats) MarshalJSON() ([]byte, error) {
   539  	if uncles := ([]*types.Header)(s); len(uncles) > 0 {
   540  		return json.Marshal(uncles)
   541  	}
   542  	return []byte("[]"), nil
   543  }
   544  
   545  // reportBlock retrieves the current chain head and repors it to the stats server.
   546  func (s *Service) reportBlock(ctx context.Context, conn *websocket.Conn, block *types.Block) error {
   547  	ctx, span := trace.StartSpan(ctx, "Service.reportBlock")
   548  	defer span.End()
   549  
   550  	// Gather the block details from the header or block chain
   551  	details := s.assembleBlockStats(ctx, block)
   552  
   553  	// Assemble the block report and send it to the server
   554  	log.Trace("Sending new block to netstats", "number", details.Number, "hash", details.Hash)
   555  
   556  	stats := map[string]interface{}{
   557  		"id":    s.node,
   558  		"block": details,
   559  	}
   560  	report := map[string][]interface{}{
   561  		"emit": {"block", stats},
   562  	}
   563  	return websocket.JSON.Send(conn, report)
   564  }
   565  
   566  // assembleBlockStats retrieves any required metadata to report a single block
   567  // and assembles the block stats. If block is nil, the current head is processed.
   568  func (s *Service) assembleBlockStats(ctx context.Context, block *types.Block) *blockStats {
   569  	ctx, span := trace.StartSpan(ctx, "Service.assembleBlockStats")
   570  	defer span.End()
   571  
   572  	// Gather the block infos from the local blockchain
   573  	var (
   574  		header  *types.Header
   575  		td      *big.Int
   576  		txs     []txStats
   577  		txCount int
   578  		uncles  []*types.Header
   579  	)
   580  	if s.eth != nil {
   581  		// Full nodes have all needed information available
   582  		if block == nil {
   583  			block = s.eth.BlockChain().CurrentBlock()
   584  		}
   585  		header = block.Header()
   586  		td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
   587  
   588  		txCount = len(block.Transactions())
   589  		txs = make([]txStats, txCount)
   590  		for i, tx := range block.Transactions() {
   591  			txs[i].Hash = tx.Hash()
   592  		}
   593  		uncles = block.Uncles()
   594  	} else {
   595  		// Light nodes would need on-demand lookups for transactions/uncles, skip
   596  		if block != nil {
   597  			header = block.Header()
   598  		} else {
   599  			header = s.les.BlockChain().CurrentHeader()
   600  		}
   601  		td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
   602  		txs = []txStats{}
   603  	}
   604  	// Assemble and return the block stats
   605  	author, _ := s.engine.Author(header)
   606  
   607  	return &blockStats{
   608  		Number:     header.Number,
   609  		Hash:       header.Hash(),
   610  		ParentHash: header.ParentHash,
   611  		Timestamp:  header.Time,
   612  		Miner:      author,
   613  		GasUsed:    header.GasUsed,
   614  		GasLimit:   header.GasLimit,
   615  		Diff:       header.Difficulty.String(),
   616  		TotalDiff:  td.String(),
   617  		TxCount:    uint64(txCount),
   618  		Txs:        txs,
   619  		TxHash:     header.TxHash,
   620  		Root:       header.Root,
   621  		Uncles:     uncles,
   622  	}
   623  }
   624  
   625  // reportHistory retrieves the most recent batch of blocks and reports it to the
   626  // stats server.
   627  func (s *Service) reportHistory(ctx context.Context, conn *websocket.Conn, list []uint64) error {
   628  	ctx, span := trace.StartSpan(ctx, "Service.reportHistory")
   629  	defer span.End()
   630  
   631  	// Figure out the indexes that need reporting
   632  	indexes := make([]uint64, 0, historyUpdateRange)
   633  	if len(list) > 0 {
   634  		// Specific indexes requested, send them back in particular
   635  		indexes = append(indexes, list...)
   636  	} else {
   637  		// No indexes requested, send back the top ones
   638  		var head int64
   639  		if s.eth != nil {
   640  			head = s.eth.BlockChain().CurrentHeader().Number.Int64()
   641  		} else {
   642  			head = s.les.BlockChain().CurrentHeader().Number.Int64()
   643  		}
   644  		start := head - historyUpdateRange + 1
   645  		if start < 0 {
   646  			start = 0
   647  		}
   648  		for i := uint64(start); i <= uint64(head); i++ {
   649  			indexes = append(indexes, i)
   650  		}
   651  	}
   652  	// Gather the batch of blocks to report
   653  	history := make([]*blockStats, len(indexes))
   654  	for i, number := range indexes {
   655  		// Retrieve the next block if it's known to us
   656  		var block *types.Block
   657  		if s.eth != nil {
   658  			block = s.eth.BlockChain().GetBlockByNumber(number)
   659  		} else {
   660  			if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil {
   661  				block = types.NewBlockWithHeader(header)
   662  			}
   663  		}
   664  		// If we do have the block, add to the history and continue
   665  		if block != nil {
   666  			history[len(history)-1-i] = s.assembleBlockStats(ctx, block)
   667  			continue
   668  		}
   669  		// Ran out of blocks, cut the report short and send
   670  		history = history[len(history)-i:]
   671  		break
   672  	}
   673  	// Assemble the history report and send it to the server
   674  	if len(history) > 0 {
   675  		log.Trace("Sending historical blocks to netstats", "first", history[0].Number, "last", history[len(history)-1].Number)
   676  	} else {
   677  		log.Trace("No history to send to stats server")
   678  	}
   679  	stats := map[string]interface{}{
   680  		"id":      s.node,
   681  		"history": history,
   682  	}
   683  	report := map[string][]interface{}{
   684  		"emit": {"history", stats},
   685  	}
   686  	return websocket.JSON.Send(conn, report)
   687  }
   688  
   689  // pendStats is the information to report about pending transactions.
   690  type pendStats struct {
   691  	Pending int `json:"pending"`
   692  }
   693  
   694  // reportPending retrieves the current number of pending transactions and reports
   695  // it to the stats server.
   696  func (s *Service) reportPending(ctx context.Context, conn *websocket.Conn) error {
   697  	ctx, span := trace.StartSpan(ctx, "Service.reportPending")
   698  	defer span.End()
   699  
   700  	// Retrieve the pending count from the local blockchain
   701  	var pending int
   702  	if s.eth != nil {
   703  		pending, _ = s.eth.TxPool().StatsCtx(ctx)
   704  	} else {
   705  		pending = s.les.TxPool().Stats()
   706  	}
   707  	// Assemble the transaction stats and send it to the server
   708  	log.Trace("Sending pending transactions to netstats", "count", pending)
   709  
   710  	stats := map[string]interface{}{
   711  		"id": s.node,
   712  		"stats": &pendStats{
   713  			Pending: pending,
   714  		},
   715  	}
   716  	report := map[string][]interface{}{
   717  		"emit": {"pending", stats},
   718  	}
   719  	return websocket.JSON.Send(conn, report)
   720  }
   721  
   722  // nodeStats is the information to report about the local node.
   723  type nodeStats struct {
   724  	Active   bool `json:"active"`
   725  	Syncing  bool `json:"syncing"`
   726  	Mining   bool `json:"mining"`
   727  	Hashrate int  `json:"hashrate"`
   728  	Peers    int  `json:"peers"`
   729  	GasPrice int  `json:"gasPrice"`
   730  	Uptime   int  `json:"uptime"`
   731  }
   732  
   733  // reportPending retrieves various stats about the node at the networking and
   734  // mining layer and reports it to the stats server.
   735  func (s *Service) reportStats(ctx context.Context, conn *websocket.Conn) error {
   736  	ctx, span := trace.StartSpan(ctx, "Service.reportStats")
   737  	defer span.End()
   738  
   739  	// Gather the syncing and mining infos from the local miner instance
   740  	var (
   741  		mining   bool
   742  		syncing  bool
   743  		gasprice int
   744  	)
   745  	if s.eth != nil {
   746  		mining = s.eth.Miner().Mining()
   747  
   748  		sync := s.eth.Downloader().Progress()
   749  		syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
   750  
   751  		price, _ := s.eth.ApiBackend.SuggestPrice(context.Background())
   752  		gasprice = int(price.Uint64())
   753  	} else {
   754  		sync := s.les.Downloader().Progress()
   755  		syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
   756  	}
   757  	// Assemble the node stats and send it to the server
   758  	log.Trace("Sending node details to netstats")
   759  
   760  	stats := map[string]interface{}{
   761  		"id": s.node,
   762  		"stats": &nodeStats{
   763  			Active:   true,
   764  			Mining:   mining,
   765  			Peers:    s.server.PeerCount(),
   766  			GasPrice: gasprice,
   767  			Syncing:  syncing,
   768  			Uptime:   100,
   769  		},
   770  	}
   771  	report := map[string][]interface{}{
   772  		"emit": {"stats", stats},
   773  	}
   774  	return websocket.JSON.Send(conn, report)
   775  }