status-im/status-go

View on GitHub
node/geth_node.go

Summary

Maintainability
A
0 mins
Test Coverage
C
71%
package node

import (
    "errors"
    "fmt"
    "os"
    "path/filepath"

    "github.com/syndtr/goleveldb/leveldb"

    "github.com/ethereum/go-ethereum/accounts"
    "github.com/ethereum/go-ethereum/log"
    "github.com/ethereum/go-ethereum/node"
    "github.com/ethereum/go-ethereum/p2p"
    "github.com/ethereum/go-ethereum/p2p/discv5"
    "github.com/ethereum/go-ethereum/p2p/enode"
    "github.com/ethereum/go-ethereum/p2p/nat"

    "github.com/status-im/status-go/eth-node/crypto"
    "github.com/status-im/status-go/params"
)

// Errors related to node and services creation.
var (
    ErrNodeMakeFailureFormat                      = "error creating p2p node: %s"
    ErrWakuServiceRegistrationFailure             = errors.New("failed to register the Waku service")
    ErrWakuV2ServiceRegistrationFailure           = errors.New("failed to register the WakuV2 service")
    ErrLightEthRegistrationFailure                = errors.New("failed to register the LES service")
    ErrLightEthRegistrationFailureUpstreamEnabled = errors.New("failed to register the LES service, upstream is also configured")
    ErrPersonalServiceRegistrationFailure         = errors.New("failed to register the personal api service")
    ErrStatusServiceRegistrationFailure           = errors.New("failed to register the Status service")
    ErrPeerServiceRegistrationFailure             = errors.New("failed to register the Peer service")
)

// All general log messages in this package should be routed through this logger.
var logger = log.New("package", "status-go/node")

// MakeNode creates a geth node entity
func MakeNode(config *params.NodeConfig, accs *accounts.Manager, db *leveldb.DB) (*node.Node, error) {
    // If DataDir is empty, it means we want to create an ephemeral node
    // keeping data only in memory.
    if config.DataDir != "" {
        // make sure data directory exists
        if err := os.MkdirAll(filepath.Clean(config.DataDir), os.ModePerm); err != nil {
            return nil, fmt.Errorf("make node: make data directory: %v", err)
        }

        // make sure keys directory exists
        if err := os.MkdirAll(filepath.Clean(config.KeyStoreDir), os.ModePerm); err != nil {
            return nil, fmt.Errorf("make node: make keys directory: %v", err)
        }
    }

    stackConfig, err := newGethNodeConfig(config)
    if err != nil {
        return nil, err
    }

    stack, err := node.New(stackConfig)
    if err != nil {
        return nil, fmt.Errorf(ErrNodeMakeFailureFormat, err.Error())
    }

    return stack, nil
}

// newGethNodeConfig returns default stack configuration for mobile client node
func newGethNodeConfig(config *params.NodeConfig) (*node.Config, error) {
    // NOTE: I haven't changed anything related to this parameters, but
    // it seems they were previously ignored if set to 0, but now they seem
    // to be used, so they need to be set to something
    maxPeers := 100
    maxPendingPeers := 100

    if config.MaxPeers != 0 {
        maxPeers = config.MaxPeers
    }

    if config.MaxPendingPeers != 0 {
        maxPendingPeers = config.MaxPendingPeers
    }

    nc := &node.Config{
        DataDir:           config.DataDir,
        KeyStoreDir:       config.KeyStoreDir,
        UseLightweightKDF: true,
        NoUSB:             true,
        Name:              config.Name,
        Version:           config.Version,
        P2P: p2p.Config{
            NoDiscovery:     true, // we always use only v5 server
            ListenAddr:      config.ListenAddr,
            NAT:             nat.Any(),
            MaxPeers:        maxPeers,
            MaxPendingPeers: maxPendingPeers,
        },
    }

    if config.IPCEnabled {
        // use well-known defaults
        if config.IPCFile == "" {
            config.IPCFile = "geth.ipc"
        }

        nc.IPCPath = config.IPCFile
    }

    if config.HTTPEnabled {
        nc.HTTPModules = config.FormatAPIModules()
        nc.HTTPHost = config.HTTPHost
        nc.HTTPPort = config.HTTPPort
        nc.HTTPVirtualHosts = config.HTTPVirtualHosts
        nc.HTTPCors = config.HTTPCors
    }

    if config.WSEnabled {
        nc.WSModules = config.FormatAPIModules()
        nc.WSHost = config.WSHost
        nc.WSPort = config.WSPort
        // FIXME: this is a temporary solution to allow all origins
        nc.WSOrigins = []string{"*"}
    }

    if config.ClusterConfig.Enabled {
        nc.P2P.BootstrapNodesV5 = parseNodesV5(config.ClusterConfig.BootNodes)
        nc.P2P.StaticNodes = parseNodes(config.ClusterConfig.StaticNodes)
    }

    if config.NodeKey != "" {
        sk, err := crypto.HexToECDSA(config.NodeKey)
        if err != nil {
            return nil, err
        }
        // override node's private key
        nc.P2P.PrivateKey = sk
    }

    return nc, nil
}

// parseNodes creates list of enode.Node out of enode strings.
func parseNodes(enodes []string) []*enode.Node {
    var nodes []*enode.Node
    for _, item := range enodes {
        parsedPeer, err := enode.ParseV4(item)
        if err == nil {
            nodes = append(nodes, parsedPeer)
        } else {
            logger.Error("Failed to parse enode", "enode", item, "err", err)
        }

    }
    return nodes
}

// parseNodesV5 creates list of discv5.Node out of enode strings.
func parseNodesV5(enodes []string) []*discv5.Node {
    var nodes []*discv5.Node
    for _, enode := range enodes {
        parsedPeer, err := discv5.ParseNode(enode)

        if err == nil {
            nodes = append(nodes, parsedPeer)
        } else {
            logger.Error("Failed to parse enode", "enode", enode, "err", err)
        }
    }
    return nodes
}

func parseNodesToNodeID(enodes []string) []enode.ID {
    nodeIDs := make([]enode.ID, 0, len(enodes))
    for _, node := range parseNodes(enodes) {
        nodeIDs = append(nodeIDs, node.ID())
    }
    return nodeIDs
}