node: refactor package node (#21105)
This PR significantly changes the APIs for instantiating Ethereum nodes in a Go program. The new APIs are not backwards-compatible, but we feel that this is made up for by the much simpler way of registering services on node.Node. You can find more information and rationale in the design document: https://gist.github.com/renaynay/5bec2de19fde66f4d04c535fd24f0775. There is also a new feature in Node's Go API: it is now possible to register arbitrary handlers on the user-facing HTTP server. In geth, this facility is used to enable GraphQL. There is a single minor change relevant for geth users in this PR: The GraphQL API is no longer available separately from the JSON-RPC HTTP server. If you want GraphQL, you need to enable it using the ./geth --http --graphql flag combination. The --graphql.port and --graphql.addr flags are no longer available.
This commit is contained in:
@ -36,9 +36,12 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/gorilla/websocket"
|
||||
@ -56,23 +59,33 @@ const (
|
||||
chainHeadChanSize = 10
|
||||
)
|
||||
|
||||
type txPool interface {
|
||||
// SubscribeNewTxsEvent should return an event subscription of
|
||||
// NewTxsEvent and send events to the given channel.
|
||||
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
|
||||
// backend encompasses the bare-minimum functionality needed for ethstats reporting
|
||||
type backend interface {
|
||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||
SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
|
||||
CurrentHeader() *types.Header
|
||||
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||
GetTd(ctx context.Context, hash common.Hash) *big.Int
|
||||
Stats() (pending int, queued int)
|
||||
Downloader() *downloader.Downloader
|
||||
}
|
||||
|
||||
type blockChain interface {
|
||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||
// fullNodeBackend encompasses the functionality necessary for a full node
|
||||
// reporting to ethstats
|
||||
type fullNodeBackend interface {
|
||||
backend
|
||||
Miner() *miner.Miner
|
||||
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
||||
CurrentBlock() *types.Block
|
||||
SuggestPrice(ctx context.Context) (*big.Int, error)
|
||||
}
|
||||
|
||||
// Service implements an Ethereum netstats reporting daemon that pushes local
|
||||
// chain statistics up to a monitoring server.
|
||||
type Service struct {
|
||||
server *p2p.Server // Peer-to-peer server to retrieve networking infos
|
||||
eth *eth.Ethereum // Full Ethereum service if monitoring a full node
|
||||
les *les.LightEthereum // Light Ethereum service if monitoring a light node
|
||||
engine consensus.Engine // Consensus engine to retrieve variadic block fields
|
||||
server *p2p.Server // Peer-to-peer server to retrieve networking infos
|
||||
backend backend
|
||||
engine consensus.Engine // Consensus engine to retrieve variadic block fields
|
||||
|
||||
node string // Name of the node to display on the monitoring page
|
||||
pass string // Password to authorize access to the monitoring page
|
||||
@ -83,50 +96,37 @@ type Service struct {
|
||||
}
|
||||
|
||||
// New returns a monitoring service ready for stats reporting.
|
||||
func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) {
|
||||
func New(node *node.Node, backend backend, engine consensus.Engine, url string) error {
|
||||
// Parse the netstats connection url
|
||||
re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)")
|
||||
parts := re.FindStringSubmatch(url)
|
||||
if len(parts) != 5 {
|
||||
return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
|
||||
return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
|
||||
}
|
||||
// Assemble and return the stats service
|
||||
var engine consensus.Engine
|
||||
if ethServ != nil {
|
||||
engine = ethServ.Engine()
|
||||
} else {
|
||||
engine = lesServ.Engine()
|
||||
ethstats := &Service{
|
||||
backend: backend,
|
||||
engine: engine,
|
||||
server: node.Server(),
|
||||
node: parts[1],
|
||||
pass: parts[3],
|
||||
host: parts[4],
|
||||
pongCh: make(chan struct{}),
|
||||
histCh: make(chan []uint64, 1),
|
||||
}
|
||||
return &Service{
|
||||
eth: ethServ,
|
||||
les: lesServ,
|
||||
engine: engine,
|
||||
node: parts[1],
|
||||
pass: parts[3],
|
||||
host: parts[4],
|
||||
pongCh: make(chan struct{}),
|
||||
histCh: make(chan []uint64, 1),
|
||||
}, nil
|
||||
|
||||
node.RegisterLifecycle(ethstats)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Protocols implements node.Service, returning the P2P network protocols used
|
||||
// by the stats service (nil as it doesn't use the devp2p overlay network).
|
||||
func (s *Service) Protocols() []p2p.Protocol { return nil }
|
||||
|
||||
// APIs implements node.Service, returning the RPC API endpoints provided by the
|
||||
// stats service (nil as it doesn't provide any user callable APIs).
|
||||
func (s *Service) APIs() []rpc.API { return nil }
|
||||
|
||||
// Start implements node.Service, starting up the monitoring and reporting daemon.
|
||||
func (s *Service) Start(server *p2p.Server) error {
|
||||
s.server = server
|
||||
// Start implements node.Lifecycle, starting up the monitoring and reporting daemon.
|
||||
func (s *Service) Start() error {
|
||||
go s.loop()
|
||||
|
||||
log.Info("Stats daemon started")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements node.Service, terminating the monitoring and reporting daemon.
|
||||
// Stop implements node.Lifecycle, terminating the monitoring and reporting daemon.
|
||||
func (s *Service) Stop() error {
|
||||
log.Info("Stats daemon stopped")
|
||||
return nil
|
||||
@ -136,22 +136,12 @@ func (s *Service) Stop() error {
|
||||
// until termination.
|
||||
func (s *Service) loop() {
|
||||
// Subscribe to chain events to execute updates on
|
||||
var blockchain blockChain
|
||||
var txpool txPool
|
||||
if s.eth != nil {
|
||||
blockchain = s.eth.BlockChain()
|
||||
txpool = s.eth.TxPool()
|
||||
} else {
|
||||
blockchain = s.les.BlockChain()
|
||||
txpool = s.les.TxPool()
|
||||
}
|
||||
|
||||
chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
|
||||
headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh)
|
||||
headSub := s.backend.SubscribeChainHeadEvent(chainHeadCh)
|
||||
defer headSub.Unsubscribe()
|
||||
|
||||
txEventCh := make(chan core.NewTxsEvent, txChanSize)
|
||||
txSub := txpool.SubscribeNewTxsEvent(txEventCh)
|
||||
txSub := s.backend.SubscribeNewTxsEvent(txEventCh)
|
||||
defer txSub.Unsubscribe()
|
||||
|
||||
// Start a goroutine that exhausts the subscriptions to avoid events piling up
|
||||
@ -560,13 +550,15 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
|
||||
txs []txStats
|
||||
uncles []*types.Header
|
||||
)
|
||||
if s.eth != nil {
|
||||
// Full nodes have all needed information available
|
||||
|
||||
// check if backend is a full node
|
||||
fullBackend, ok := s.backend.(fullNodeBackend)
|
||||
if ok {
|
||||
if block == nil {
|
||||
block = s.eth.BlockChain().CurrentBlock()
|
||||
block = fullBackend.CurrentBlock()
|
||||
}
|
||||
header = block.Header()
|
||||
td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
|
||||
td = fullBackend.GetTd(context.Background(), header.Hash())
|
||||
|
||||
txs = make([]txStats, len(block.Transactions()))
|
||||
for i, tx := range block.Transactions() {
|
||||
@ -578,11 +570,12 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
|
||||
if block != nil {
|
||||
header = block.Header()
|
||||
} else {
|
||||
header = s.les.BlockChain().CurrentHeader()
|
||||
header = s.backend.CurrentHeader()
|
||||
}
|
||||
td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
|
||||
td = s.backend.GetTd(context.Background(), header.Hash())
|
||||
txs = []txStats{}
|
||||
}
|
||||
|
||||
// Assemble and return the block stats
|
||||
author, _ := s.engine.Author(header)
|
||||
|
||||
@ -613,12 +606,7 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
|
||||
indexes = append(indexes, list...)
|
||||
} else {
|
||||
// No indexes requested, send back the top ones
|
||||
var head int64
|
||||
if s.eth != nil {
|
||||
head = s.eth.BlockChain().CurrentHeader().Number.Int64()
|
||||
} else {
|
||||
head = s.les.BlockChain().CurrentHeader().Number.Int64()
|
||||
}
|
||||
head := s.backend.CurrentHeader().Number.Int64()
|
||||
start := head - historyUpdateRange + 1
|
||||
if start < 0 {
|
||||
start = 0
|
||||
@ -630,12 +618,13 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
|
||||
// Gather the batch of blocks to report
|
||||
history := make([]*blockStats, len(indexes))
|
||||
for i, number := range indexes {
|
||||
fullBackend, ok := s.backend.(fullNodeBackend)
|
||||
// Retrieve the next block if it's known to us
|
||||
var block *types.Block
|
||||
if s.eth != nil {
|
||||
block = s.eth.BlockChain().GetBlockByNumber(number)
|
||||
if ok {
|
||||
block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(number)) // TODO ignore error here ?
|
||||
} else {
|
||||
if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil {
|
||||
if header, _ := s.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(number)); header != nil {
|
||||
block = types.NewBlockWithHeader(header)
|
||||
}
|
||||
}
|
||||
@ -673,12 +662,7 @@ type pendStats struct {
|
||||
// it to the stats server.
|
||||
func (s *Service) reportPending(conn *websocket.Conn) error {
|
||||
// Retrieve the pending count from the local blockchain
|
||||
var pending int
|
||||
if s.eth != nil {
|
||||
pending, _ = s.eth.TxPool().Stats()
|
||||
} else {
|
||||
pending = s.les.TxPool().Stats()
|
||||
}
|
||||
pending, _ := s.backend.Stats()
|
||||
// Assemble the transaction stats and send it to the server
|
||||
log.Trace("Sending pending transactions to ethstats", "count", pending)
|
||||
|
||||
@ -705,7 +689,7 @@ type nodeStats struct {
|
||||
Uptime int `json:"uptime"`
|
||||
}
|
||||
|
||||
// reportPending retrieves various stats about the node at the networking and
|
||||
// reportStats retrieves various stats about the node at the networking and
|
||||
// mining layer and reports it to the stats server.
|
||||
func (s *Service) reportStats(conn *websocket.Conn) error {
|
||||
// Gather the syncing and mining infos from the local miner instance
|
||||
@ -715,18 +699,20 @@ func (s *Service) reportStats(conn *websocket.Conn) error {
|
||||
syncing bool
|
||||
gasprice int
|
||||
)
|
||||
if s.eth != nil {
|
||||
mining = s.eth.Miner().Mining()
|
||||
hashrate = int(s.eth.Miner().HashRate())
|
||||
// check if backend is a full node
|
||||
fullBackend, ok := s.backend.(fullNodeBackend)
|
||||
if ok {
|
||||
mining = fullBackend.Miner().Mining()
|
||||
hashrate = int(fullBackend.Miner().HashRate())
|
||||
|
||||
sync := s.eth.Downloader().Progress()
|
||||
syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
|
||||
sync := fullBackend.Downloader().Progress()
|
||||
syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
|
||||
|
||||
price, _ := s.eth.APIBackend.SuggestPrice(context.Background())
|
||||
price, _ := fullBackend.SuggestPrice(context.Background())
|
||||
gasprice = int(price.Uint64())
|
||||
} else {
|
||||
sync := s.les.Downloader().Progress()
|
||||
syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
|
||||
sync := s.backend.Downloader().Progress()
|
||||
syncing = s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
|
||||
}
|
||||
// Assemble the node stats and send it to the server
|
||||
log.Trace("Sending node details to ethstats")
|
||||
|
Reference in New Issue
Block a user