github.com/core-coin/go-core/v2@v2.1.9/xcbstats/xcbstats.go (about)

     1  // Copyright 2016 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package xcbstats implements the network stats reporting service.
    18  package xcbstats
    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/gorilla/websocket"
    35  
    36  	"github.com/core-coin/go-core/v2/common"
    37  	"github.com/core-coin/go-core/v2/common/mclock"
    38  	"github.com/core-coin/go-core/v2/consensus"
    39  	"github.com/core-coin/go-core/v2/core"
    40  	"github.com/core-coin/go-core/v2/core/types"
    41  	"github.com/core-coin/go-core/v2/event"
    42  	"github.com/core-coin/go-core/v2/les"
    43  	"github.com/core-coin/go-core/v2/log"
    44  	"github.com/core-coin/go-core/v2/miner"
    45  	"github.com/core-coin/go-core/v2/node"
    46  	"github.com/core-coin/go-core/v2/p2p"
    47  	"github.com/core-coin/go-core/v2/rpc"
    48  	"github.com/core-coin/go-core/v2/xcb"
    49  	"github.com/core-coin/go-core/v2/xcb/downloader"
    50  )
    51  
    52  const (
    53  	// historyUpdateRange is the number of blocks a node should report upon login or
    54  	// history request.
    55  	historyUpdateRange = 50
    56  
    57  	// txChanSize is the size of channel listening to NewTxsEvent.
    58  	// The number is referenced from the size of tx pool.
    59  	txChanSize = 4096
    60  	// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
    61  	chainHeadChanSize = 10
    62  )
    63  
    64  // backend encompasses the bare-minimum functionality needed for xcbstats reporting
    65  type backend interface {
    66  	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
    67  	SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
    68  	CurrentHeader() *types.Header
    69  	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    70  	GetTd(ctx context.Context, hash common.Hash) *big.Int
    71  	Stats() (pending int, queued int)
    72  	Downloader() *downloader.Downloader
    73  }
    74  
    75  // fullNodeBackend encompasses the functionality necessary for a full node
    76  // reporting to xcbstats
    77  type fullNodeBackend interface {
    78  	backend
    79  	Miner() *miner.Miner
    80  	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    81  	CurrentBlock() *types.Block
    82  	SuggestPrice(ctx context.Context) (*big.Int, error)
    83  }
    84  
    85  // Service implements an Core netstats reporting daemon that pushes local
    86  // chain statistics up to a monitoring server.
    87  type Service struct {
    88  	server  *p2p.Server // Peer-to-peer server to retrieve networking infos
    89  	backend backend
    90  	engine  consensus.Engine // Consensus engine to retrieve variadic block fields
    91  
    92  	node string // Name of the node to display on the monitoring page
    93  	pass string // Password to authorize access to the monitoring page
    94  	host string // Remote address of the monitoring service
    95  
    96  	pongCh chan struct{} // Pong notifications are fed into this channel
    97  	histCh chan []uint64 // History request block numbers are fed into this channel
    98  
    99  }
   100  
   101  // connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
   102  // websocket.
   103  //
   104  // From Gorilla websocket docs:
   105  //
   106  //	Connections support one concurrent reader and one concurrent writer.
   107  //	Applications are responsible for ensuring that no more than one goroutine calls the write methods
   108  //	  - NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel
   109  //	concurrently and that no more than one goroutine calls the read methods
   110  //	  - NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler
   111  //	concurrently.
   112  //	The Close and WriteControl methods can be called concurrently with all other methods.
   113  type connWrapper struct {
   114  	conn *websocket.Conn
   115  
   116  	rlock sync.Mutex
   117  	wlock sync.Mutex
   118  }
   119  
   120  func newConnectionWrapper(conn *websocket.Conn) *connWrapper {
   121  	return &connWrapper{conn: conn}
   122  }
   123  
   124  // WriteJSON wraps corresponding method on the websocket but is safe for concurrent calling
   125  func (w *connWrapper) WriteJSON(v interface{}) error {
   126  	w.wlock.Lock()
   127  	defer w.wlock.Unlock()
   128  
   129  	return w.conn.WriteJSON(v)
   130  }
   131  
   132  // ReadJSON wraps corresponding method on the websocket but is safe for concurrent calling
   133  func (w *connWrapper) ReadJSON(v interface{}) error {
   134  	w.rlock.Lock()
   135  	defer w.rlock.Unlock()
   136  
   137  	return w.conn.ReadJSON(v)
   138  }
   139  
   140  // Close wraps corresponding method on the websocket but is safe for concurrent calling
   141  func (w *connWrapper) Close() error {
   142  	// The Close and WriteControl methods can be called concurrently with all other methods,
   143  	// so the mutex is not used here
   144  	return w.conn.Close()
   145  }
   146  
   147  // New returns a monitoring service ready for stats reporting.
   148  func New(node *node.Node, backend backend, engine consensus.Engine, url string) error {
   149  	// Parse the netstats connection url
   150  	re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)")
   151  	parts := re.FindStringSubmatch(url)
   152  	if len(parts) != 5 {
   153  		return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
   154  	}
   155  	xcbstats := &Service{
   156  		backend: backend,
   157  		engine:  engine,
   158  		server:  node.Server(),
   159  		node:    parts[1],
   160  		pass:    parts[3],
   161  		host:    parts[4],
   162  		pongCh:  make(chan struct{}),
   163  		histCh:  make(chan []uint64, 1),
   164  	}
   165  
   166  	node.RegisterLifecycle(xcbstats)
   167  	return nil
   168  }
   169  
   170  // Start implements node.Lifecycle, starting up the monitoring and reporting daemon.
   171  func (s *Service) Start() error {
   172  	go s.loop()
   173  
   174  	log.Info("Stats daemon started")
   175  	return nil
   176  }
   177  
   178  // Stop implements node.Lifecycle, terminating the monitoring and reporting daemon.
   179  func (s *Service) Stop() error {
   180  	log.Info("Stats daemon stopped")
   181  	return nil
   182  }
   183  
   184  // loop keeps trying to connect to the netstats server, reporting chain events
   185  // until termination.
   186  func (s *Service) loop() {
   187  	// Subscribe to chain events to execute updates on
   188  	chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
   189  	headSub := s.backend.SubscribeChainHeadEvent(chainHeadCh)
   190  	defer headSub.Unsubscribe()
   191  
   192  	txEventCh := make(chan core.NewTxsEvent, txChanSize)
   193  	txSub := s.backend.SubscribeNewTxsEvent(txEventCh)
   194  	defer txSub.Unsubscribe()
   195  
   196  	// Start a goroutine that exhausts the subscriptions to avoid events piling up
   197  	var (
   198  		quitCh = make(chan struct{})
   199  		headCh = make(chan *types.Block, 1)
   200  		txCh   = make(chan struct{}, 1)
   201  	)
   202  	go func() {
   203  		var lastTx mclock.AbsTime
   204  
   205  	HandleLoop:
   206  		for {
   207  			select {
   208  			// Notify of chain head events, but drop if too frequent
   209  			case head := <-chainHeadCh:
   210  				select {
   211  				case headCh <- head.Block:
   212  				default:
   213  				}
   214  
   215  			// Notify of new transaction events, but drop if too frequent
   216  			case <-txEventCh:
   217  				if time.Duration(mclock.Now()-lastTx) < time.Second {
   218  					continue
   219  				}
   220  				lastTx = mclock.Now()
   221  
   222  				select {
   223  				case txCh <- struct{}{}:
   224  				default:
   225  				}
   226  
   227  			// node stopped
   228  			case <-txSub.Err():
   229  				break HandleLoop
   230  			case <-headSub.Err():
   231  				break HandleLoop
   232  			}
   233  		}
   234  		close(quitCh)
   235  	}()
   236  
   237  	// Resolve the URL, defaulting to TLS, but falling back to none too
   238  	path := fmt.Sprintf("%s/api", s.host)
   239  	urls := []string{path}
   240  
   241  	// url.Parse and url.IsAbs is unsuitable (https://github.com/golang/go/issues/19779)
   242  	if !strings.Contains(path, "://") {
   243  		urls = []string{"wss://" + path, "ws://" + path}
   244  	}
   245  
   246  	errTimer := time.NewTimer(0)
   247  	defer errTimer.Stop()
   248  	// Loop reporting until termination
   249  	for {
   250  		select {
   251  		case <-quitCh:
   252  			return
   253  		case <-errTimer.C:
   254  			// Establish a websocket connection to the server on any supported URL
   255  			var (
   256  				conn *connWrapper
   257  				err  error
   258  			)
   259  			dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second}
   260  			header := make(http.Header)
   261  			header.Set("origin", "http://localhost")
   262  			for _, url := range urls {
   263  				c, _, e := dialer.Dial(url, header)
   264  				err = e
   265  				if err == nil {
   266  					conn = newConnectionWrapper(c)
   267  					break
   268  				}
   269  			}
   270  			if err != nil {
   271  				log.Warn("Stats server unreachable", "err", err)
   272  				errTimer.Reset(10 * time.Second)
   273  				continue
   274  			}
   275  			// Authenticate the client with the server
   276  			if err = s.login(conn); err != nil {
   277  				log.Warn("Stats login failed", "err", err)
   278  				conn.Close()
   279  				errTimer.Reset(10 * time.Second)
   280  				continue
   281  			}
   282  			go s.readLoop(conn)
   283  
   284  			// Send the initial stats so our node looks decent from the get go
   285  			if err = s.report(conn); err != nil {
   286  				log.Warn("Initial stats report failed", "err", err)
   287  				conn.Close()
   288  				errTimer.Reset(0)
   289  				continue
   290  			}
   291  			// Keep sending status updates until the connection breaks
   292  			fullReport := time.NewTicker(15 * time.Second)
   293  
   294  			for err == nil {
   295  				select {
   296  				case <-quitCh:
   297  					fullReport.Stop()
   298  					// Make sure the connection is closed
   299  					conn.Close()
   300  					return
   301  
   302  				case <-fullReport.C:
   303  					if err = s.report(conn); err != nil {
   304  						log.Warn("Full stats report failed", "err", err)
   305  					}
   306  				case list := <-s.histCh:
   307  					if err = s.reportHistory(conn, list); err != nil {
   308  						log.Warn("Requested history report failed", "err", err)
   309  					}
   310  				case head := <-headCh:
   311  					if err = s.reportBlock(conn, head); err != nil {
   312  						log.Warn("Block stats report failed", "err", err)
   313  					}
   314  					if err = s.reportPending(conn); err != nil {
   315  						log.Warn("Post-block transaction stats report failed", "err", err)
   316  					}
   317  				case <-txCh:
   318  					if err = s.reportPending(conn); err != nil {
   319  						log.Warn("Transaction stats report failed", "err", err)
   320  					}
   321  				}
   322  			}
   323  			fullReport.Stop()
   324  
   325  			// Close the current connection and establish a new one
   326  			conn.Close()
   327  			errTimer.Reset(0)
   328  		}
   329  	}
   330  }
   331  
   332  // readLoop loops as long as the connection is alive and retrieves data packets
   333  // from the network socket. If any of them match an active request, it forwards
   334  // it, if they themselves are requests it initiates a reply, and lastly it drops
   335  // unknown packets.
   336  func (s *Service) readLoop(conn *connWrapper) {
   337  	// If the read loop exists, close the connection
   338  	defer conn.Close()
   339  
   340  	for {
   341  		// Retrieve the next generic network packet and bail out on error
   342  		var blob json.RawMessage
   343  		if err := conn.ReadJSON(&blob); err != nil {
   344  			log.Warn("Failed to retrieve stats server message", "err", err)
   345  			return
   346  		}
   347  		// If the network packet is a system ping, respond to it directly
   348  		var ping string
   349  		if err := json.Unmarshal(blob, &ping); err == nil && strings.HasPrefix(ping, "primus::ping::") {
   350  			if err := conn.WriteJSON(strings.Replace(ping, "ping", "pong", -1)); err != nil {
   351  				log.Warn("Failed to respond to system ping message", "err", err)
   352  				return
   353  			}
   354  			continue
   355  		}
   356  		// Not a system ping, try to decode an actual state message
   357  		var msg map[string][]interface{}
   358  		if err := json.Unmarshal(blob, &msg); err != nil {
   359  			log.Warn("Failed to decode stats server message", "err", err)
   360  			return
   361  		}
   362  		log.Trace("Received message from stats server", "msg", msg)
   363  		if len(msg["emit"]) == 0 {
   364  			log.Warn("Stats server sent non-broadcast", "msg", msg)
   365  			return
   366  		}
   367  		command, ok := msg["emit"][0].(string)
   368  		if !ok {
   369  			log.Warn("Invalid stats server message type", "type", msg["emit"][0])
   370  			return
   371  		}
   372  		// If the message is a ping reply, deliver (someone must be listening!)
   373  		if len(msg["emit"]) == 2 && command == "node-pong" {
   374  			select {
   375  			case s.pongCh <- struct{}{}:
   376  				// Pong delivered, continue listening
   377  				continue
   378  			default:
   379  				// Ping routine dead, abort
   380  				log.Warn("Stats server pinger seems to have died")
   381  				return
   382  			}
   383  		}
   384  		// If the message is a history request, forward to the event processor
   385  		if len(msg["emit"]) == 2 && command == "history" {
   386  			// Make sure the request is valid and doesn't crash us
   387  			request, ok := msg["emit"][1].(map[string]interface{})
   388  			if !ok {
   389  				log.Warn("Invalid stats history request", "msg", msg["emit"][1])
   390  				select {
   391  				case s.histCh <- nil: // Treat it as an no indexes request
   392  				default:
   393  				}
   394  				continue
   395  			}
   396  			list, ok := request["list"].([]interface{})
   397  			if !ok {
   398  				log.Warn("Invalid stats history block list", "list", request["list"])
   399  				return
   400  			}
   401  			// Convert the block number list to an integer list
   402  			numbers := make([]uint64, len(list))
   403  			for i, num := range list {
   404  				n, ok := num.(float64)
   405  				if !ok {
   406  					log.Warn("Invalid stats history block number", "number", num)
   407  					return
   408  				}
   409  				numbers[i] = uint64(n)
   410  			}
   411  			select {
   412  			case s.histCh <- numbers:
   413  				continue
   414  			default:
   415  			}
   416  		}
   417  		// Report anything else and continue
   418  		log.Info("Unknown stats message", "msg", msg)
   419  	}
   420  }
   421  
   422  // nodeInfo is the collection of meta information about a node that is displayed
   423  // on the monitoring page.
   424  type nodeInfo struct {
   425  	Name     string `json:"name"`
   426  	Node     string `json:"node"`
   427  	Port     int    `json:"port"`
   428  	Network  string `json:"net"`
   429  	Protocol string `json:"protocol"`
   430  	API      string `json:"api"`
   431  	Os       string `json:"os"`
   432  	OsVer    string `json:"os_v"`
   433  	Client   string `json:"client"`
   434  	History  bool   `json:"canUpdateHistory"`
   435  }
   436  
   437  // authMsg is the authentication infos needed to login to a monitoring server.
   438  type authMsg struct {
   439  	ID     string   `json:"id"`
   440  	Info   nodeInfo `json:"info"`
   441  	Secret string   `json:"secret"`
   442  }
   443  
   444  // login tries to authorize the client at the remote server.
   445  func (s *Service) login(conn *connWrapper) error {
   446  	// Construct and send the login authentication
   447  	infos := s.server.NodeInfo()
   448  
   449  	var network, protocol string
   450  	if info := infos.Protocols["xcb"]; info != nil {
   451  		network = fmt.Sprintf("%d", info.(*xcb.NodeInfo).Network)
   452  		protocol = fmt.Sprintf("xcb/%d", xcb.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 xcbstats 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 xcbstats", "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  	EnergyUsed  uint64         `json:"energyUsed"`
   551  	EnergyLimit uint64         `json:"energyLimit"`
   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 xcbstats", "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  		EnergyUsed:  header.EnergyUsed,
   640  		EnergyLimit: header.EnergyLimit,
   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 xcbstats", "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 xcbstats", "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  	EnergyPrice int  `json:"energyPrice"`
   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  		energyprice 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  		energyprice = 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 xcbstats")
   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  			EnergyPrice: energyprice,
   779  			Syncing:     syncing,
   780  			Uptime:      100,
   781  		},
   782  	}
   783  	report := map[string][]interface{}{
   784  		"emit": {"stats", stats},
   785  	}
   786  	return conn.WriteJSON(report)
   787  }