status-go/geth/node_manager.go

290 lines
7.9 KiB
Go

package geth
/*
#include <stddef.h>
#include <stdbool.h>
extern bool StatusServiceSignalEvent( const char *jsonEvent );
*/
import "C"
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"sync"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rpc"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv2"
)
// SelectedExtKey is a container for currently selected (logged in) account
type SelectedExtKey struct {
Address common.Address
AccountKey *accounts.Key
SubAccounts []accounts.Account
}
// NodeManager manages Status node (which abstracts contained geth node)
type NodeManager struct {
node *Node // reference to Status node
services *NodeServiceStack // default stack of services running on geth node
SelectedAccount *SelectedExtKey // account that was processed during the last call to SelectAccount()
}
// NodeServiceStack contains "standard" node services (which are always available)
type NodeServiceStack struct {
lightEthereum *les.LightEthereum // LES service
whisperService *whisper.Whisper // Whisper service
rpcClient *rpc.Client // RPC client
jailedRequestQueue *JailedRequestQueue // bridge via which jail notifies node of incoming requests
}
var (
ErrDataDirPreprocessingFailed = errors.New("failed to pre-process data directory")
ErrInvalidGethNode = errors.New("no running geth node detected")
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
ErrInvalidLightEthereumService = errors.New("can not retrieve LES service")
ErrInvalidClient = errors.New("RPC client is not properly initialized")
ErrInvalidJailedRequestQueue = errors.New("jailed request queue is not properly initialized")
ErrNodeMakeFailure = errors.New("error creating p2p node")
ErrNodeStartFailure = errors.New("error starting p2p node")
)
var (
nodeManagerInstance *NodeManager
createOnce sync.Once
)
// CreateAndRunNode creates and starts running Geth node locally (exposing given RPC port along the way)
func CreateAndRunNode(dataDir string, rpcPort int) error {
nodeManager := NewNodeManager(dataDir, rpcPort)
if nodeManager.NodeInited() {
nodeManager.RunNode()
<-nodeManager.node.started // block until node is ready
return nil
}
return ErrNodeStartFailure
}
// NewNodeManager makes new instance of node manager
func NewNodeManager(dataDir string, rpcPort int) *NodeManager {
createOnce.Do(func() {
nodeManagerInstance = &NodeManager{
services: &NodeServiceStack{
jailedRequestQueue: NewJailedRequestsQueue(),
},
}
nodeManagerInstance.node = MakeNode(dataDir, rpcPort)
})
return nodeManagerInstance
}
// NodeManagerInstance exposes node manager instance
func NodeManagerInstance() *NodeManager {
return nodeManagerInstance
}
// RunNode starts Geth node
func (m *NodeManager) RunNode() {
go func() {
m.StartNode()
if _, err := m.AccountManager(); err != nil {
glog.V(logger.Warn).Infoln(ErrInvalidAccountManager)
}
if err := m.node.geth.Service(&m.services.whisperService); err != nil {
glog.V(logger.Warn).Infoln("cannot get whisper service:", err)
}
if err := m.node.geth.Service(&m.services.lightEthereum); err != nil {
glog.V(logger.Warn).Infoln("cannot get light ethereum service:", err)
}
// setup handlers
lightEthereum, err := m.LightEthereumService()
if err != nil {
panic("service stack misses LES")
}
lightEthereum.StatusBackend.SetTransactionQueueHandler(onSendTransactionRequest)
lightEthereum.StatusBackend.SetAccountsFilterHandler(onAccountsListRequest)
lightEthereum.StatusBackend.SetTransactionReturnHandler(onSendTransactionReturn)
m.services.rpcClient, err = m.node.geth.Attach()
if err != nil {
glog.V(logger.Warn).Infoln("cannot get RPC client service:", ErrInvalidClient)
}
// @TODO Remove after LES supports discover out of box
m.populateStaticPeers()
m.onNodeStarted() // node started, notify listeners
m.node.geth.Wait()
}()
}
// StartNode starts running P2P node
func (m *NodeManager) StartNode() {
if m == nil || !m.NodeInited() {
panic(ErrInvalidGethNode)
}
if err := m.node.geth.Start(); err != nil {
panic(fmt.Sprintf("%v: %v", ErrNodeStartFailure, err))
}
// allow interrupting running nodes
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt)
defer signal.Stop(sigc)
<-sigc
glog.V(logger.Info).Infoln("Got interrupt, shutting down...")
go m.node.geth.Stop()
for i := 3; i > 0; i-- {
<-sigc
if i > 1 {
glog.V(logger.Info).Infof("Already shutting down, interrupt %d more times for panic.", i-1)
}
}
panic("interrupted!")
}()
}
// HasNode checks whether manager has initialized node attached
func (m *NodeManager) NodeInited() bool {
if m == nil || !m.node.Inited() {
return false
}
return true
}
// AccountManager exposes reference to accounts manager
func (m *NodeManager) AccountManager() (*accounts.Manager, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
}
return m.node.geth.AccountManager(), nil
}
// LightEthereumService exposes LES
func (m *NodeManager) LightEthereumService() (*les.LightEthereum, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
}
if m.services.lightEthereum == nil {
return nil, ErrInvalidLightEthereumService
}
return m.services.lightEthereum, nil
}
// WhisperService exposes Whisper service
func (m *NodeManager) WhisperService() (*whisper.Whisper, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
}
if m.services.whisperService == nil {
return nil, ErrInvalidWhisperService
}
return m.services.whisperService, nil
}
// RPCClient exposes Geth's RPC client
func (m *NodeManager) RPCClient() (*rpc.Client, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
}
if m.services.rpcClient == nil {
return nil, ErrInvalidClient
}
return m.services.rpcClient, nil
}
// JailedRequestQueue exposes reference to queue of jailed requests
func (m *NodeManager) JailedRequestQueue() (*JailedRequestQueue, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
}
if m.services.jailedRequestQueue == nil {
return nil, ErrInvalidJailedRequestQueue
}
return m.services.jailedRequestQueue, nil
}
// AddPeer adds new peer node
func (m *NodeManager) AddPeer(url string) (bool, error) {
if m == nil || !m.NodeInited() {
return false, ErrInvalidGethNode
}
server := m.node.geth.Server()
if server == nil {
return false, ErrInvalidGethNode
}
// Try to add the url as a static peer and return
parsedNode, err := discover.ParseNode(url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
server.AddPeer(parsedNode)
return true, nil
}
// onNodeStarted sends upward notification letting the app know that Geth node is ready to be used
func (m *NodeManager) onNodeStarted() {
// notify local listener
m.node.started <- struct{}{}
close(m.node.started)
// send signal up to native app
event := GethEvent{
Type: EventNodeStarted,
Event: struct{}{},
}
body, _ := json.Marshal(&event)
C.StatusServiceSignalEvent(C.CString(string(body)))
}
// populateStaticPeers connects current node with our publicly available LES cluster
func (m *NodeManager) populateStaticPeers() {
// manually add static nodes (LES auto-discovery is not stable yet)
configFile, err := ioutil.ReadFile(filepath.Join("../data", "static-nodes.json"))
if err != nil {
return
}
var enodes []string
if err = json.Unmarshal(configFile, &enodes); err != nil {
return
}
for _, enode := range enodes {
m.AddPeer(enode)
}
}