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