github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/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/kisexp/xdchain/common"
    35  	"github.com/kisexp/xdchain/common/mclock"
    36  	"github.com/kisexp/xdchain/consensus"
    37  	"github.com/kisexp/xdchain/core"
    38  	"github.com/kisexp/xdchain/core/types"
    39  	"github.com/kisexp/xdchain/eth"
    40  	"github.com/kisexp/xdchain/eth/downloader"
    41  	"github.com/kisexp/xdchain/event"
    42  	"github.com/kisexp/xdchain/les"
    43  	"github.com/kisexp/xdchain/log"
    44  	"github.com/kisexp/xdchain/miner"
    45  	"github.com/kisexp/xdchain/node"
    46  	"github.com/kisexp/xdchain/p2p"
    47  	"github.com/kisexp/xdchain/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  	//must pass engine protocol name for quorum
   449  	p := s.engine.Protocol()
   450  	if info := infos.Protocols[p.Name]; info != nil {
   451  		network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network)
   452  		protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
   453  	} else {
   454  		network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network)
   455  		protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0])
   456  	}
   457  	auth := &authMsg{
   458  		ID: s.node,
   459  		Info: nodeInfo{
   460  			Name:     s.node,
   461  			Node:     infos.Name,
   462  			Port:     infos.Ports.Listener,
   463  			Network:  network,
   464  			Protocol: protocol,
   465  			API:      "No",
   466  			Os:       runtime.GOOS,
   467  			OsVer:    runtime.GOARCH,
   468  			Client:   "0.1.1",
   469  			History:  true,
   470  		},
   471  		Secret: s.pass,
   472  	}
   473  	login := map[string][]interface{}{
   474  		"emit": {"hello", auth},
   475  	}
   476  	if err := conn.WriteJSON(login); err != nil {
   477  		return err
   478  	}
   479  	// Retrieve the remote ack or connection termination
   480  	var ack map[string][]string
   481  	if err := conn.ReadJSON(&ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" {
   482  		return errors.New("unauthorized")
   483  	}
   484  	return nil
   485  }
   486  
   487  // report collects all possible data to report and send it to the stats server.
   488  // This should only be used on reconnects or rarely to avoid overloading the
   489  // server. Use the individual methods for reporting subscribed events.
   490  func (s *Service) report(conn *connWrapper) error {
   491  	if err := s.reportLatency(conn); err != nil {
   492  		return err
   493  	}
   494  	if err := s.reportBlock(conn, nil); err != nil {
   495  		return err
   496  	}
   497  	if err := s.reportPending(conn); err != nil {
   498  		return err
   499  	}
   500  	if err := s.reportStats(conn); err != nil {
   501  		return err
   502  	}
   503  	return nil
   504  }
   505  
   506  // reportLatency sends a ping request to the server, measures the RTT time and
   507  // finally sends a latency update.
   508  func (s *Service) reportLatency(conn *connWrapper) error {
   509  	// Send the current time to the ethstats server
   510  	start := time.Now()
   511  
   512  	ping := map[string][]interface{}{
   513  		"emit": {"node-ping", map[string]string{
   514  			"id":         s.node,
   515  			"clientTime": start.String(),
   516  		}},
   517  	}
   518  	if err := conn.WriteJSON(ping); err != nil {
   519  		return err
   520  	}
   521  	// Wait for the pong request to arrive back
   522  	select {
   523  	case <-s.pongCh:
   524  		// Pong delivered, report the latency
   525  	case <-time.After(5 * time.Second):
   526  		// Ping timeout, abort
   527  		return errors.New("ping timed out")
   528  	}
   529  	latency := strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000))
   530  
   531  	// Send back the measured latency
   532  	log.Trace("Sending measured latency to ethstats", "latency", latency)
   533  
   534  	stats := map[string][]interface{}{
   535  		"emit": {"latency", map[string]string{
   536  			"id":      s.node,
   537  			"latency": latency,
   538  		}},
   539  	}
   540  	return conn.WriteJSON(stats)
   541  }
   542  
   543  // blockStats is the information to report about individual blocks.
   544  type blockStats struct {
   545  	Number     *big.Int       `json:"number"`
   546  	Hash       common.Hash    `json:"hash"`
   547  	ParentHash common.Hash    `json:"parentHash"`
   548  	Timestamp  *big.Int       `json:"timestamp"`
   549  	Miner      common.Address `json:"miner"`
   550  	GasUsed    uint64         `json:"gasUsed"`
   551  	GasLimit   uint64         `json:"gasLimit"`
   552  	Diff       string         `json:"difficulty"`
   553  	TotalDiff  string         `json:"totalDifficulty"`
   554  	Txs        []txStats      `json:"transactions"`
   555  	TxHash     common.Hash    `json:"transactionsRoot"`
   556  	Root       common.Hash    `json:"stateRoot"`
   557  	Uncles     uncleStats     `json:"uncles"`
   558  }
   559  
   560  // txStats is the information to report about individual transactions.
   561  type txStats struct {
   562  	Hash common.Hash `json:"hash"`
   563  }
   564  
   565  // uncleStats is a custom wrapper around an uncle array to force serializing
   566  // empty arrays instead of returning null for them.
   567  type uncleStats []*types.Header
   568  
   569  func (s uncleStats) MarshalJSON() ([]byte, error) {
   570  	if uncles := ([]*types.Header)(s); len(uncles) > 0 {
   571  		return json.Marshal(uncles)
   572  	}
   573  	return []byte("[]"), nil
   574  }
   575  
   576  // reportBlock retrieves the current chain head and reports it to the stats server.
   577  func (s *Service) reportBlock(conn *connWrapper, block *types.Block) error {
   578  	// Gather the block details from the header or block chain
   579  	details := s.assembleBlockStats(block)
   580  
   581  	// Assemble the block report and send it to the server
   582  	log.Trace("Sending new block to ethstats", "number", details.Number, "hash", details.Hash)
   583  
   584  	stats := map[string]interface{}{
   585  		"id":    s.node,
   586  		"block": details,
   587  	}
   588  	report := map[string][]interface{}{
   589  		"emit": {"block", stats},
   590  	}
   591  	return conn.WriteJSON(report)
   592  }
   593  
   594  // assembleBlockStats retrieves any required metadata to report a single block
   595  // and assembles the block stats. If block is nil, the current head is processed.
   596  func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
   597  	// Gather the block infos from the local blockchain
   598  	var (
   599  		header *types.Header
   600  		td     *big.Int
   601  		txs    []txStats
   602  		uncles []*types.Header
   603  	)
   604  
   605  	// check if backend is a full node
   606  	fullBackend, ok := s.backend.(fullNodeBackend)
   607  	if ok {
   608  		if block == nil {
   609  			block = fullBackend.CurrentBlock()
   610  		}
   611  		header = block.Header()
   612  		td = fullBackend.GetTd(context.Background(), header.Hash())
   613  
   614  		txs = make([]txStats, len(block.Transactions()))
   615  		for i, tx := range block.Transactions() {
   616  			txs[i].Hash = tx.Hash()
   617  		}
   618  		uncles = block.Uncles()
   619  	} else {
   620  		// Light nodes would need on-demand lookups for transactions/uncles, skip
   621  		if block != nil {
   622  			header = block.Header()
   623  		} else {
   624  			header = s.backend.CurrentHeader()
   625  		}
   626  		td = s.backend.GetTd(context.Background(), header.Hash())
   627  		txs = []txStats{}
   628  	}
   629  
   630  	// Assemble and return the block stats
   631  	author, _ := s.engine.Author(header)
   632  
   633  	return &blockStats{
   634  		Number:     header.Number,
   635  		Hash:       header.Hash(),
   636  		ParentHash: header.ParentHash,
   637  		Timestamp:  new(big.Int).SetUint64(header.Time),
   638  		Miner:      author,
   639  		GasUsed:    header.GasUsed,
   640  		GasLimit:   header.GasLimit,
   641  		Diff:       header.Difficulty.String(),
   642  		TotalDiff:  td.String(),
   643  		Txs:        txs,
   644  		TxHash:     header.TxHash,
   645  		Root:       header.Root,
   646  		Uncles:     uncles,
   647  	}
   648  }
   649  
   650  // reportHistory retrieves the most recent batch of blocks and reports it to the
   651  // stats server.
   652  func (s *Service) reportHistory(conn *connWrapper, list []uint64) error {
   653  	// Figure out the indexes that need reporting
   654  	indexes := make([]uint64, 0, historyUpdateRange)
   655  	if len(list) > 0 {
   656  		// Specific indexes requested, send them back in particular
   657  		indexes = append(indexes, list...)
   658  	} else {
   659  		// No indexes requested, send back the top ones
   660  		head := s.backend.CurrentHeader().Number.Int64()
   661  		start := head - historyUpdateRange + 1
   662  		if start < 0 {
   663  			start = 0
   664  		}
   665  		for i := uint64(start); i <= uint64(head); i++ {
   666  			indexes = append(indexes, i)
   667  		}
   668  	}
   669  	// Gather the batch of blocks to report
   670  	history := make([]*blockStats, len(indexes))
   671  	for i, number := range indexes {
   672  		fullBackend, ok := s.backend.(fullNodeBackend)
   673  		// Retrieve the next block if it's known to us
   674  		var block *types.Block
   675  		if ok {
   676  			block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(number)) // TODO ignore error here ?
   677  		} else {
   678  			if header, _ := s.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(number)); header != nil {
   679  				block = types.NewBlockWithHeader(header)
   680  			}
   681  		}
   682  		// If we do have the block, add to the history and continue
   683  		if block != nil {
   684  			history[len(history)-1-i] = s.assembleBlockStats(block)
   685  			continue
   686  		}
   687  		// Ran out of blocks, cut the report short and send
   688  		history = history[len(history)-i:]
   689  		break
   690  	}
   691  	// Assemble the history report and send it to the server
   692  	if len(history) > 0 {
   693  		log.Trace("Sending historical blocks to ethstats", "first", history[0].Number, "last", history[len(history)-1].Number)
   694  	} else {
   695  		log.Trace("No history to send to stats server")
   696  	}
   697  	stats := map[string]interface{}{
   698  		"id":      s.node,
   699  		"history": history,
   700  	}
   701  	report := map[string][]interface{}{
   702  		"emit": {"history", stats},
   703  	}
   704  	return conn.WriteJSON(report)
   705  }
   706  
   707  // pendStats is the information to report about pending transactions.
   708  type pendStats struct {
   709  	Pending int `json:"pending"`
   710  }
   711  
   712  // reportPending retrieves the current number of pending transactions and reports
   713  // it to the stats server.
   714  func (s *Service) reportPending(conn *connWrapper) error {
   715  	// Retrieve the pending count from the local blockchain
   716  	pending, _ := s.backend.Stats()
   717  	// Assemble the transaction stats and send it to the server
   718  	log.Trace("Sending pending transactions to ethstats", "count", pending)
   719  
   720  	stats := map[string]interface{}{
   721  		"id": s.node,
   722  		"stats": &pendStats{
   723  			Pending: pending,
   724  		},
   725  	}
   726  	report := map[string][]interface{}{
   727  		"emit": {"pending", stats},
   728  	}
   729  	return conn.WriteJSON(report)
   730  }
   731  
   732  // nodeStats is the information to report about the local node.
   733  type nodeStats struct {
   734  	Active   bool `json:"active"`
   735  	Syncing  bool `json:"syncing"`
   736  	Mining   bool `json:"mining"`
   737  	Hashrate int  `json:"hashrate"`
   738  	Peers    int  `json:"peers"`
   739  	GasPrice int  `json:"gasPrice"`
   740  	Uptime   int  `json:"uptime"`
   741  }
   742  
   743  // reportStats retrieves various stats about the node at the networking and
   744  // mining layer and reports it to the stats server.
   745  func (s *Service) reportStats(conn *connWrapper) error {
   746  	// Gather the syncing and mining infos from the local miner instance
   747  	var (
   748  		mining   bool
   749  		hashrate int
   750  		syncing  bool
   751  		gasprice int
   752  	)
   753  	// check if backend is a full node
   754  	fullBackend, ok := s.backend.(fullNodeBackend)
   755  	if ok {
   756  		mining = fullBackend.Miner().Mining()
   757  		hashrate = int(fullBackend.Miner().HashRate())
   758  
   759  		sync := fullBackend.Downloader().Progress()
   760  		syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
   761  
   762  		price, _ := fullBackend.SuggestPrice(context.Background())
   763  		gasprice = int(price.Uint64())
   764  	} else {
   765  		sync := s.backend.Downloader().Progress()
   766  		syncing = s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
   767  	}
   768  	// Assemble the node stats and send it to the server
   769  	log.Trace("Sending node details to ethstats")
   770  
   771  	stats := map[string]interface{}{
   772  		"id": s.node,
   773  		"stats": &nodeStats{
   774  			Active:   true,
   775  			Mining:   mining,
   776  			Hashrate: hashrate,
   777  			Peers:    s.server.PeerCount(),
   778  			GasPrice: gasprice,
   779  			Syncing:  syncing,
   780  			Uptime:   100,
   781  		},
   782  	}
   783  	report := map[string][]interface{}{
   784  		"emit": {"stats", stats},
   785  	}
   786  	return conn.WriteJSON(report)
   787  }