Allow to start ephemeral StatusNode (#829)

This change will greatly simplify writing unit tests when a node is required but data persistence is irrelevant.

I also Introduced some refactoring and unit tests for `StatusNode`.
This commit is contained in:
Adam Babik 2018-04-16 14:36:09 +02:00 committed by GitHub
parent ef160e8720
commit 364f88efd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 514 additions and 291 deletions

View File

@ -164,7 +164,7 @@ func main() {
exitCode := syncAndStopNode(interruptCh, backend.StatusNode(), *syncAndExit)
// Call was interrupted. Wait for graceful shutdown.
if exitCode == -1 {
if node, err := backend.StatusNode().GethNode(); err == nil && node != nil {
if node := backend.StatusNode().GethNode(); node != nil {
node.Wait()
}
return
@ -173,14 +173,11 @@ func main() {
os.Exit(exitCode)
}
node, err := backend.StatusNode().GethNode()
if err != nil {
logger.Error("Getting node failed", "error", err)
return
node := backend.StatusNode().GethNode()
if node != nil {
// wait till node has been stopped
node.Wait()
}
// wait till node has been stopped
node.Wait()
}
// startDebug starts the debugging API server.
@ -195,9 +192,9 @@ func startCollectingStats(interruptCh <-chan struct{}, statusNode *node.StatusNo
logger.Info("Starting stats", "stats", *statsAddr)
node, err := statusNode.GethNode()
if err != nil {
logger.Error("Failed to run metrics because could not get node", "error", err)
node := statusNode.GethNode()
if node == nil {
logger.Error("Failed to run metrics because it could not get the node")
return
}

View File

@ -112,12 +112,6 @@ func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
err = b.statusNode.Start(config)
if err != nil {
switch err.(type) {
case node.RPCClientError:
err = fmt.Errorf("%v: %v", node.ErrRPCClient, err)
case node.EthNodeError:
err = fmt.Errorf("%v: %v", node.ErrNodeStartFailure, err)
}
signal.Send(signal.Envelope{
Type: signal.EventNodeCrashed,
Event: signal.NodeCrashEvent{
@ -162,11 +156,7 @@ func (b *StatusBackend) RestartNode() error {
if !b.IsNodeRunning() {
return node.ErrNoRunningNode
}
config, err := b.statusNode.Config()
if err != nil {
return err
}
newcfg := *config
newcfg := *(b.statusNode.Config())
if err := b.stopNode(); err != nil {
return err
}
@ -178,11 +168,7 @@ func (b *StatusBackend) RestartNode() error {
func (b *StatusBackend) ResetChainData() error {
b.mu.Lock()
defer b.mu.Unlock()
config, err := b.statusNode.Config()
if err != nil {
return err
}
newcfg := *config
newcfg := *(b.statusNode.Config())
if err := b.stopNode(); err != nil {
return err
}
@ -217,10 +203,7 @@ func (b *StatusBackend) getVerifiedAccount(password string) (*account.SelectedEx
b.log.Error("failed to get a selected account", "err", err)
return nil, err
}
config, err := b.StatusNode().Config()
if err != nil {
return nil, err
}
config := b.StatusNode().Config()
_, err = b.accountManager.VerifyAccountPassword(config.KeyStoreDir, selectedAccount.Address.String(), password)
if err != nil {
b.log.Error("failed to verify account", "account", selectedAccount.Address.String(), "error", err)
@ -269,7 +252,7 @@ func (b *StatusBackend) DiscardTransactions(ids []string) map[string]error {
func (b *StatusBackend) registerHandlers() error {
rpcClient := b.StatusNode().RPCClient()
if rpcClient == nil {
return node.ErrRPCClient
return errors.New("RPC client unavailable")
}
rpcClient.RegisterHandler(params.AccountsMethodName, func(context.Context, ...interface{}) (interface{}, error) {

View File

@ -27,14 +27,11 @@ import (
"github.com/status-im/status-go/shhext"
)
// node-related errors
// Errors related to node and services creation.
var (
ErrEthServiceRegistrationFailure = errors.New("failed to register the Ethereum service")
ErrNodeMakeFailure = errors.New("error creating p2p node")
ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
ErrNodeMakeFailure = errors.New("error creating p2p node")
ErrNodeRunFailure = errors.New("error running p2p node")
ErrNodeStartFailure = errors.New("error starting p2p node")
)
// All general log messages in this package should be routed through this logger.
@ -42,17 +39,20 @@ var logger = log.New("package", "status-go/geth/node")
// MakeNode create a geth node entity
func MakeNode(config *params.NodeConfig) (*node.Node, error) {
// make sure data directory exists
if err := os.MkdirAll(filepath.Join(config.DataDir), os.ModePerm); err != nil {
return nil, err
// 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)
}
}
// make sure keys directory exists
if err := os.MkdirAll(filepath.Join(config.KeyStoreDir), os.ModePerm); err != nil {
return nil, err
}
// configure required node (should you need to update node's config, e.g. add bootstrap nodes, see node.Config)
stackConfig := defaultEmbeddedNodeConfig(config)
if len(config.NodeKeyFile) > 0 {
@ -71,14 +71,14 @@ func MakeNode(config *params.NodeConfig) (*node.Node, error) {
return nil, ErrNodeMakeFailure
}
// Start Ethereum service if we are not expected to use an upstream server.
// start Ethereum service if we are not expected to use an upstream server
if !config.UpstreamConfig.Enabled {
if err := activateEthService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrEthServiceRegistrationFailure, err)
if err := activateLightEthService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrLightEthRegistrationFailure, err)
}
}
// start Whisper service
// start Whisper service.
if err := activateShhService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err)
}
@ -104,13 +104,9 @@ func defaultEmbeddedNodeConfig(config *params.NodeConfig) *node.Config {
MaxPendingPeers: config.MaxPendingPeers,
},
IPCPath: makeIPCPath(config),
HTTPCors: []string{"*"},
HTTPCors: nil,
HTTPModules: config.FormatAPIModules(),
HTTPVirtualHosts: []string{"localhost"},
WSHost: makeWSHost(config),
WSPort: config.WSPort,
WSOrigins: []string{"*"},
WSModules: config.FormatAPIModules(),
}
if config.RPCEnabled {
@ -118,7 +114,7 @@ func defaultEmbeddedNodeConfig(config *params.NodeConfig) *node.Config {
nc.HTTPPort = config.HTTPPort
}
if config.ClusterConfig.Enabled {
if config.ClusterConfig != nil && config.ClusterConfig.Enabled {
nc.P2P.StaticNodes = parseNodes(config.ClusterConfig.StaticNodes)
nc.P2P.BootstrapNodesV5 = parseNodesV5(config.ClusterConfig.BootNodes)
}
@ -126,9 +122,9 @@ func defaultEmbeddedNodeConfig(config *params.NodeConfig) *node.Config {
return nc
}
// activateEthService configures and registers the eth.Ethereum service with a given node.
func activateEthService(stack *node.Node, config *params.NodeConfig) error {
if !config.LightEthConfig.Enabled {
// activateLightEthService configures and registers the eth.Ethereum service with a given node.
func activateLightEthService(stack *node.Node, config *params.NodeConfig) error {
if config.LightEthConfig == nil || !config.LightEthConfig.Enabled {
logger.Info("LES protocol is disabled")
return nil
}
@ -147,18 +143,14 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
ethConf.NetworkId = config.NetworkID
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, &ethConf)
}); err != nil {
return fmt.Errorf("%v: %v", ErrLightEthRegistrationFailure, err)
}
return nil
})
}
// activateShhService configures Whisper and adds it to the given node.
func activateShhService(stack *node.Node, config *params.NodeConfig) (err error) {
if !config.WhisperConfig.Enabled {
if config.WhisperConfig == nil || !config.WhisperConfig.Enabled {
logger.Info("SHH protocol is disabled")
return nil
}
@ -239,15 +231,6 @@ func makeIPCPath(config *params.NodeConfig) string {
return path.Join(config.DataDir, config.IPCFile)
}
// makeWSHost returns WS-RPC Server host, given enabled/disabled flag
func makeWSHost(config *params.NodeConfig) string {
if !config.WSEnabled {
return ""
}
return config.WSHost
}
// parseNodes creates list of discover.Node out of enode strings.
func parseNodes(enodes []string) []*discover.Node {
nodes := make([]*discover.Node, len(enodes))

View File

@ -5,26 +5,25 @@ import (
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
. "github.com/status-im/status-go/t/utils"
"github.com/status-im/status-go/geth/params"
"github.com/stretchr/testify/require"
)
func TestWhisperLightModeEnabledSetsEmptyBloomFilter(t *testing.T) {
config, err := MakeTestNodeConfig(GetNetworkID())
require.NoError(t, err)
config.WhisperConfig.LightClient = true
node, nodeErr := MakeNode(config)
require.NoError(t, nodeErr)
require.NoError(t, node.Start())
config := params.NodeConfig{
WhisperConfig: &params.WhisperConfig{
Enabled: true,
LightClient: true,
},
}
node := New()
require.NoError(t, node.Start(&config))
defer func() {
err := node.Stop()
require.NoError(t, err)
require.NoError(t, node.Stop())
}()
var whisper *whisper.Whisper
err = node.Service(&whisper)
require.NoError(t, err)
require.NoError(t, node.gethService(&whisper))
bloomFilter := whisper.BloomFilter()
expectedEmptyBloomFilter := make([]byte, 64)
@ -33,20 +32,19 @@ func TestWhisperLightModeEnabledSetsEmptyBloomFilter(t *testing.T) {
}
func TestWhisperLightModeEnabledSetsNilBloomFilter(t *testing.T) {
config, err := MakeTestNodeConfig(GetNetworkID())
require.NoError(t, err)
config.WhisperConfig.LightClient = false
node, nodeErr := MakeNode(config)
require.NoError(t, nodeErr)
require.NoError(t, node.Start())
config := params.NodeConfig{
WhisperConfig: &params.WhisperConfig{
Enabled: true,
LightClient: false,
},
}
node := New()
require.NoError(t, node.Start(&config))
defer func() {
err := node.Stop()
require.NoError(t, err)
require.NoError(t, node.Stop())
}()
var whisper *whisper.Whisper
err = node.Service(&whisper)
require.NoError(t, err)
require.NoError(t, node.gethService(&whisper))
require.Nil(t, whisper.BloomFilter())
}

View File

@ -24,23 +24,17 @@ import (
"github.com/status-im/status-go/geth/rpc"
)
// tickerResolution is the delta to check blockchain sync progress.
const tickerResolution = time.Second
// errors
var (
ErrNodeExists = errors.New("node is already running")
ErrNodeRunning = errors.New("node is already running")
ErrNoGethNode = errors.New("geth node is not available")
ErrNoRunningNode = errors.New("there is no running node")
ErrInvalidStatusNode = errors.New("status node is not properly initialized")
ErrInvalidService = errors.New("service is unavailable")
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
ErrAccountKeyStoreMissing = errors.New("account key store is not set")
ErrRPCClient = errors.New("failed to init RPC client")
)
// RPCClientError reported when rpc client is initialized.
type RPCClientError error
// EthNodeError is reported when node crashed on start up.
type EthNodeError error
// StatusNode abstracts contained geth node and provides helper methods to
// interact with it.
type StatusNode struct {
@ -65,52 +59,67 @@ func New() *StatusNode {
}
}
// Config exposes reference to running node's configuration
func (n *StatusNode) Config() *params.NodeConfig {
n.mu.RLock()
defer n.mu.RUnlock()
return n.config
}
// GethNode returns underlying geth node.
func (n *StatusNode) GethNode() *node.Node {
n.mu.RLock()
defer n.mu.RUnlock()
return n.gethNode
}
// Start starts current StatusNode, will fail if it's already started.
func (n *StatusNode) Start(config *params.NodeConfig, services ...node.ServiceConstructor) error {
n.mu.Lock()
defer n.mu.Unlock()
if err := n.isAvailable(); err == nil {
return ErrNodeExists
if n.isRunning() {
return ErrNodeRunning
}
return n.start(config, services)
}
// start starts current StatusNode, will fail if it's already started.
func (n *StatusNode) start(config *params.NodeConfig, services []node.ServiceConstructor) error {
ethNode, err := MakeNode(config)
if err != nil {
if err := n.createNode(config); err != nil {
return err
}
n.gethNode = ethNode
n.config = config
for _, service := range services {
if err := ethNode.Register(service); err != nil {
return err
}
if err := n.start(services); err != nil {
return err
}
// start underlying node
if err := ethNode.Start(); err != nil {
return EthNodeError(err)
}
// init RPC client for this node
if err := n.setupRPCClient(); err != nil {
n.log.Error("Failed to create an RPC client", "error", err)
return RPCClientError(err)
return err
}
// start peer pool only if Discovery V5 is enabled
if ethNode.Server().DiscV5 != nil {
if n.gethNode.Server().DiscV5 != nil {
return n.startPeerPool()
}
return nil
}
func (n *StatusNode) createNode(config *params.NodeConfig) (err error) {
n.gethNode, err = MakeNode(config)
return
}
// start starts current StatusNode, will fail if it's already started.
func (n *StatusNode) start(services []node.ServiceConstructor) error {
for _, service := range services {
if err := n.gethNode.Register(service); err != nil {
return err
}
}
return n.gethNode.Start()
}
func (n *StatusNode) setupRPCClient() (err error) {
// setup public RPC client
gethNodeClient, err := n.gethNode.AttachPublic()
@ -157,45 +166,58 @@ func (n *StatusNode) startPeerPool() error {
func (n *StatusNode) Stop() error {
n.mu.Lock()
defer n.mu.Unlock()
if !n.isRunning() {
return ErrNoRunningNode
}
return n.stop()
}
// stop will stop current StatusNode. A stopped node cannot be resumed.
func (n *StatusNode) stop() error {
if err := n.isAvailable(); err != nil {
return err
}
if n.gethNode.Server().DiscV5 != nil {
n.stopPeerPool()
if err := n.stopPeerPool(); err != nil {
n.log.Error("Error stopping the PeerPool", "error", err)
}
n.register = nil
n.peerPool = nil
n.db = nil
if err := n.gethNode.Stop(); err != nil {
return err
}
n.gethNode = nil
n.config = nil
n.rpcClient = nil
n.rpcPrivateClient = nil
// We need to clear `gethNode` because config is passed to `Start()`
// and may be completely different. Similarly with `config`.
n.gethNode = nil
n.config = nil
return nil
}
func (n *StatusNode) stopPeerPool() {
func (n *StatusNode) stopPeerPool() error {
if n.gethNode.Server().DiscV5 == nil {
return nil
}
n.register.Stop()
n.peerPool.Stop()
if err := n.db.Close(); err != nil {
n.log.Error("error closing status db", "error", err)
}
return n.db.Close()
}
// ResetChainData removes chain data if node is not running.
func (n *StatusNode) ResetChainData(config *params.NodeConfig) error {
if n.IsRunning() {
return ErrNodeExists
}
n.mu.Lock()
defer n.mu.Unlock()
if n.isRunning() {
return ErrNodeRunning
}
chainDataDir := filepath.Join(config.DataDir, config.Name, "lightchaindata")
if _, err := os.Stat(chainDataDir); os.IsNotExist(err) {
// is it really an error, if we want to remove it as next step?
return err
}
err := os.RemoveAll(chainDataDir)
@ -210,38 +232,24 @@ func (n *StatusNode) IsRunning() bool {
n.mu.RLock()
defer n.mu.RUnlock()
if err := n.isAvailable(); err != nil {
return false
}
return true
return n.isRunning()
}
// GethNode returns underlying geth node.
func (n *StatusNode) GethNode() (*node.Node, error) {
n.mu.RLock()
defer n.mu.RUnlock()
if err := n.isAvailable(); err != nil {
return nil, err
}
return n.gethNode, nil
func (n *StatusNode) isRunning() bool {
return n.gethNode != nil && n.gethNode.Server() != nil
}
// populateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
func (n *StatusNode) populateStaticPeers() error {
if err := n.isAvailable(); err != nil {
return err
}
if !n.config.ClusterConfig.Enabled {
if n.config.ClusterConfig == nil || !n.config.ClusterConfig.Enabled {
n.log.Info("Static peers are disabled")
return nil
}
for _, enode := range n.config.ClusterConfig.StaticNodes {
err := n.addPeer(enode)
if err != nil {
n.log.Warn("Static peer addition failed", "error", err)
continue
if err := n.addPeer(enode); err != nil {
n.log.Error("Static peer addition failed", "error", err)
return err
}
n.log.Info("Static peer added", "enode", enode)
}
@ -250,18 +258,14 @@ func (n *StatusNode) populateStaticPeers() error {
}
func (n *StatusNode) removeStaticPeers() error {
if !n.config.ClusterConfig.Enabled {
if n.config.ClusterConfig == nil || !n.config.ClusterConfig.Enabled {
n.log.Info("Static peers are disabled")
return nil
}
server := n.gethNode.Server()
if server == nil {
return ErrNoRunningNode
}
for _, enode := range n.config.ClusterConfig.StaticNodes {
err := n.removePeer(enode)
if err != nil {
n.log.Warn("Static peer deletion failed", "error", err)
if err := n.removePeer(enode); err != nil {
n.log.Error("Static peer deletion failed", "error", err)
return err
}
n.log.Info("Static peer deleted", "enode", enode)
@ -273,9 +277,15 @@ func (n *StatusNode) removeStaticPeers() error {
func (n *StatusNode) ReconnectStaticPeers() error {
n.mu.Lock()
defer n.mu.Unlock()
if !n.isRunning() {
return ErrNoRunningNode
}
if err := n.removeStaticPeers(); err != nil {
return err
}
return n.populateStaticPeers()
}
@ -283,20 +293,23 @@ func (n *StatusNode) ReconnectStaticPeers() error {
func (n *StatusNode) AddPeer(url string) error {
n.mu.RLock()
defer n.mu.RUnlock()
if err := n.isAvailable(); err != nil {
return err
}
return n.addPeer(url)
}
// addPeer adds new static peer node
func (n *StatusNode) addPeer(url string) error {
// Try to add the url as a static peer and return
parsedNode, err := discover.ParseNode(url)
if err != nil {
return err
}
if !n.isRunning() {
return ErrNoRunningNode
}
n.gethNode.Server().AddPeer(parsedNode)
return nil
}
@ -305,38 +318,37 @@ func (n *StatusNode) removePeer(url string) error {
if err != nil {
return err
}
if !n.isRunning() {
return ErrNoRunningNode
}
n.gethNode.Server().RemovePeer(parsedNode)
return nil
}
// PeerCount returns the number of connected peers.
func (n *StatusNode) PeerCount() int {
if !n.IsRunning() {
return 0
}
return n.gethNode.Server().PeerCount()
}
// Config exposes reference to running node's configuration
func (n *StatusNode) Config() (*params.NodeConfig, error) {
n.mu.RLock()
defer n.mu.RUnlock()
if err := n.isAvailable(); err != nil {
return nil, err
if !n.isRunning() {
return 0
}
return n.config, nil
return n.gethNode.Server().PeerCount()
}
// gethService is a wrapper for gethNode.Service which retrieves a currently
// running service registered of a specific type.
func (n *StatusNode) gethService(serviceInstance interface{}, serviceName string) error {
if err := n.isAvailable(); err != nil {
return err
func (n *StatusNode) gethService(serviceInstance interface{}) error {
if !n.isRunning() {
return ErrNoRunningNode
}
if err := n.gethNode.Service(serviceInstance); err != nil || serviceInstance == nil {
n.log.Warn("Cannot obtain ", serviceName, " service", "error", err)
return ErrInvalidService
if err := n.gethNode.Service(serviceInstance); err != nil {
return fmt.Errorf("service unavailable: %v", err)
}
return nil
@ -344,12 +356,12 @@ func (n *StatusNode) gethService(serviceInstance interface{}, serviceName string
// LightEthereumService exposes reference to LES service running on top of the node
func (n *StatusNode) LightEthereumService() (l *les.LightEthereum, err error) {
return l, n.gethService(&l, "LES")
return l, n.gethService(&l)
}
// WhisperService exposes reference to Whisper service running on top of the node
func (n *StatusNode) WhisperService() (w *whisper.Whisper, err error) {
return w, n.gethService(&w, "whisper")
return w, n.gethService(&w)
}
// AccountManager exposes reference to node's accounts manager
@ -357,14 +369,11 @@ func (n *StatusNode) AccountManager() (*accounts.Manager, error) {
n.mu.RLock()
defer n.mu.RUnlock()
if err := n.isAvailable(); err != nil {
return nil, err
if n.gethNode == nil {
return nil, ErrNoGethNode
}
accountManager := n.gethNode.AccountManager()
if accountManager == nil {
return nil, ErrInvalidAccountManager
}
return accountManager, nil
return n.gethNode.AccountManager(), nil
}
// AccountKeyStore exposes reference to accounts key store
@ -372,14 +381,11 @@ func (n *StatusNode) AccountKeyStore() (*keystore.KeyStore, error) {
n.mu.RLock()
defer n.mu.RUnlock()
if err := n.isAvailable(); err != nil {
return nil, err
}
accountManager := n.gethNode.AccountManager()
if accountManager == nil {
return nil, ErrInvalidAccountManager
if n.gethNode == nil {
return nil, ErrNoGethNode
}
accountManager := n.gethNode.AccountManager()
backends := accountManager.Backends(keystore.KeyStoreType)
if len(backends) == 0 {
return nil, ErrAccountKeyStoreMissing
@ -408,23 +414,12 @@ func (n *StatusNode) RPCPrivateClient() *rpc.Client {
return n.rpcPrivateClient
}
// isAvailable check if we have a node running and make sure is fully started
func (n *StatusNode) isAvailable() error {
if n.gethNode == nil || n.gethNode.Server() == nil {
return ErrNoRunningNode
}
return nil
}
// tickerResolution is the delta to check blockchain sync progress.
const tickerResolution = time.Second
// EnsureSync waits until blockchain synchronization
// is complete and returns.
func (n *StatusNode) EnsureSync(ctx context.Context) error {
// Don't wait for any blockchain sync for the
// local private chain as blocks are never mined.
if n.config.NetworkID == params.StatusChainNetworkID {
if n.config.NetworkID == 0 || n.config.NetworkID == params.StatusChainNetworkID {
return nil
}

View File

@ -12,7 +12,6 @@ import (
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/t/utils"
)
type TestServiceAPI struct{}
@ -52,7 +51,7 @@ func (s *testService) Stop() error {
return nil
}
func createStatusNode(config *params.NodeConfig) (*node.StatusNode, error) {
func createAndStartStatusNode(config *params.NodeConfig) (*node.StatusNode, error) {
services := []gethnode.ServiceConstructor{
func(_ *gethnode.ServiceContext) (gethnode.Service, error) {
return &testService{}, nil
@ -65,11 +64,9 @@ func createStatusNode(config *params.NodeConfig) (*node.StatusNode, error) {
func TestNodeRPCClientCallOnlyPublicAPIs(t *testing.T) {
var err error
config, err := MakeTestNodeConfig(GetNetworkID())
require.NoError(t, err)
config.APIModules = "" // no whitelisted API modules; use only public APIs
statusNode, err := createStatusNode(config)
statusNode, err := createAndStartStatusNode(&params.NodeConfig{
APIModules: "", // no whitelisted API modules; use only public APIs
})
require.NoError(t, err)
defer func() {
err := statusNode.Stop()
@ -94,11 +91,9 @@ func TestNodeRPCClientCallOnlyPublicAPIs(t *testing.T) {
func TestNodeRPCClientCallWhitelistedPrivateService(t *testing.T) {
var err error
config, err := MakeTestNodeConfig(GetNetworkID())
require.NoError(t, err)
config.APIModules = "pri"
statusNode, err := createStatusNode(config)
statusNode, err := createAndStartStatusNode(&params.NodeConfig{
APIModules: "pri",
})
require.NoError(t, err)
defer func() {
err := statusNode.Stop()
@ -118,10 +113,7 @@ func TestNodeRPCClientCallWhitelistedPrivateService(t *testing.T) {
func TestNodeRPCPrivateClientCallPrivateService(t *testing.T) {
var err error
config, err := MakeTestNodeConfig(GetNetworkID())
require.NoError(t, err)
statusNode, err := createStatusNode(config)
statusNode, err := createAndStartStatusNode(&params.NodeConfig{})
require.NoError(t, err)
defer func() {
err := statusNode.Stop()

View File

@ -0,0 +1,292 @@
package node
import (
"errors"
"io/ioutil"
"math"
"os"
"path"
"reflect"
"testing"
"time"
"github.com/ethereum/go-ethereum/les"
gethnode "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/geth/params"
"github.com/stretchr/testify/require"
)
func TestStatusNodeStart(t *testing.T) {
var err error
config := params.NodeConfig{}
n := New()
// checks before node is started
require.Nil(t, n.GethNode())
require.Nil(t, n.Config())
require.Nil(t, n.RPCClient())
require.Equal(t, 0, n.PeerCount())
_, err = n.AccountManager()
require.EqualError(t, err, ErrNoGethNode.Error())
_, err = n.AccountKeyStore()
require.EqualError(t, err, ErrNoGethNode.Error())
// start node
require.NoError(t, n.Start(&config))
// checks after node is started
require.True(t, n.IsRunning())
require.NotNil(t, n.GethNode())
require.NotNil(t, n.Config())
require.NotNil(t, n.RPCClient())
require.Equal(t, 0, n.PeerCount())
accountManager, err := n.AccountManager()
require.Nil(t, err)
require.NotNil(t, accountManager)
keyStore, err := n.AccountKeyStore()
require.Nil(t, err)
require.NotNil(t, keyStore)
// try to start already started node
require.EqualError(t, n.Start(&config), ErrNodeRunning.Error())
// stop node
require.NoError(t, n.Stop())
// try to stop already stopped node
require.EqualError(t, n.Stop(), ErrNoRunningNode.Error())
// checks after node is stopped
require.Nil(t, n.GethNode())
require.Nil(t, n.RPCClient())
require.Equal(t, 0, n.PeerCount())
_, err = n.AccountManager()
require.EqualError(t, err, ErrNoGethNode.Error())
_, err = n.AccountKeyStore()
require.EqualError(t, err, ErrNoGethNode.Error())
}
func TestStatusNodeWithDataDir(t *testing.T) {
var err error
dir, err := ioutil.TempDir("", "status-node-test")
require.NoError(t, err)
defer func() {
require.NoError(t, os.RemoveAll(dir))
}()
// keystore directory
keyStoreDir := path.Join(dir, "keystore")
err = os.MkdirAll(keyStoreDir, os.ModePerm)
require.NoError(t, err)
config := params.NodeConfig{
DataDir: dir,
KeyStoreDir: keyStoreDir,
}
n := New()
require.NoError(t, n.Start(&config))
require.NoError(t, n.Stop())
}
func TestStatusNodeServiceGetters(t *testing.T) {
config := params.NodeConfig{
WhisperConfig: &params.WhisperConfig{
Enabled: true,
},
LightEthConfig: &params.LightEthConfig{
Enabled: true,
},
}
n := New()
var (
instance interface{}
err error
)
services := []struct {
getter func() (interface{}, error)
typ reflect.Type
}{
{
getter: func() (interface{}, error) {
return n.WhisperService()
},
typ: reflect.TypeOf(&whisper.Whisper{}),
},
{
getter: func() (interface{}, error) {
return n.LightEthereumService()
},
typ: reflect.TypeOf(&les.LightEthereum{}),
},
}
for _, service := range services {
t.Run(service.typ.String(), func(t *testing.T) {
// checks before node is started
instance, err = service.getter()
require.EqualError(t, err, ErrNoRunningNode.Error())
require.Nil(t, instance)
// start node
require.NoError(t, n.Start(&config))
// checks after node is started
instance, err = service.getter()
require.NoError(t, err)
require.NotNil(t, instance)
require.Equal(t, service.typ, reflect.TypeOf(instance))
// stop node
require.NoError(t, n.Stop())
// checks after node is stopped
instance, err = service.getter()
require.EqualError(t, err, ErrNoRunningNode.Error())
require.Nil(t, instance)
})
}
}
func TestStatusNodeAddPeer(t *testing.T) {
var err error
peer, err := gethnode.New(&gethnode.Config{
P2P: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
ListenAddr: ":0",
},
})
require.NoError(t, err)
require.NoError(t, peer.Start())
defer func() {
require.NoError(t, peer.Stop())
}()
peerURL := peer.Server().Self().String()
n := New()
// checks before node is started
require.EqualError(t, n.AddPeer(peerURL), ErrNoRunningNode.Error())
// start status node
config := params.NodeConfig{
MaxPeers: math.MaxInt32,
}
require.NoError(t, n.Start(&config))
defer func() {
require.NoError(t, n.Stop())
}()
errCh := waitForPeerAsync(n, peerURL, time.Second*5)
// checks after node is started
require.NoError(t, n.AddPeer(peerURL))
require.NoError(t, <-errCh)
require.Equal(t, 1, n.PeerCount())
}
func TestStatusNodeReconnectStaticPeers(t *testing.T) {
var err error
peer, err := gethnode.New(&gethnode.Config{
P2P: p2p.Config{
MaxPeers: math.MaxInt32,
NoDiscovery: true,
ListenAddr: ":0",
},
})
require.NoError(t, err)
require.NoError(t, peer.Start())
defer func() {
require.NoError(t, peer.Stop())
}()
peerURL := peer.Server().Self().String()
n := New()
var errCh <-chan error
// checks before node is started
require.EqualError(t, n.ReconnectStaticPeers(), ErrNoRunningNode.Error())
// start status node
config := params.NodeConfig{
MaxPeers: math.MaxInt32,
ClusterConfig: &params.ClusterConfig{
Enabled: true,
StaticNodes: []string{peerURL},
},
}
errCh = waitForPeerAsync(n, peerURL, time.Second*30)
require.NoError(t, n.Start(&config))
defer func() {
require.NoError(t, n.Stop())
}()
// checks after node is started
require.NoError(t, <-errCh)
require.Equal(t, 1, n.PeerCount())
// reconnect static peers
// it takes at least 30 seconds to bring back previously connected peer
errCh = waitForPeerAsync(n, peerURL, time.Second*60)
require.NoError(t, n.ReconnectStaticPeers(), ErrNoRunningNode.Error())
require.NoError(t, <-errCh)
require.Equal(t, 1, n.PeerCount())
}
func waitForPeer(node *StatusNode, peerURL string, timeout time.Duration) error {
if !node.IsRunning() {
return ErrNoRunningNode
}
parsedPeer, err := discover.ParseNode(peerURL)
if err != nil {
return err
}
server := node.GethNode().Server()
ch := make(chan *p2p.PeerEvent)
subscription := server.SubscribeEvents(ch)
defer subscription.Unsubscribe()
for {
select {
case ev := <-ch:
if ev.Type == p2p.PeerEventTypeAdd && ev.Peer == parsedPeer.ID {
return nil
}
case err := <-subscription.Err():
if err != nil {
return err
}
case <-time.After(timeout):
// it may happen that the peer is already connected
// but even was not received
for _, p := range node.GethNode().Server().Peers() {
if p.ID() == parsedPeer.ID {
return nil
}
}
return errors.New("wait for peer: timeout")
}
}
}
func waitForPeerAsync(node *StatusNode, peerURL string, timeout time.Duration) <-chan error {
errCh := make(chan error)
go func() {
errCh <- waitForPeer(node, peerURL, timeout)
}()
return errCh
}

View File

@ -248,15 +248,6 @@ type NodeConfig struct {
// HTTPPort is the TCP port number on which to start the Geth's HTTP RPC server.
HTTPPort int
// WSHost is a host interface for the WebSocket RPC server
WSHost string
// WSPort is the TCP port number on which to start the Geth's WebSocket RPC server.
WSPort int
// WSEnabled specifies whether WS-RPC Server is enabled or not
WSEnabled bool
// IPCFile is filename of exposed IPC RPC Server
IPCFile string
@ -322,8 +313,6 @@ func NewNodeConfig(dataDir string, clstrCfgFile string, networkID uint64, devMod
HTTPPort: HTTPPort,
ListenAddr: ListenAddr,
APIModules: APIModules,
WSHost: WSHost,
WSPort: WSPort,
MaxPeers: MaxPeers,
MaxPendingPeers: MaxPendingPeers,
IPCFile: IPCFile,

View File

@ -144,8 +144,6 @@ var loadConfigTestCases = []struct {
require.Equal(t, params.HTTPPort, nodeConfig.HTTPPort)
require.Equal(t, params.HTTPHost, nodeConfig.HTTPHost)
require.True(t, nodeConfig.RPCEnabled)
require.False(t, nodeConfig.WSEnabled)
require.Equal(t, 4242, nodeConfig.WSPort)
require.True(t, nodeConfig.IPCEnabled)
require.Equal(t, 64, nodeConfig.LightEthConfig.DatabaseCache)
},

View File

@ -32,9 +32,6 @@ const (
// APIModules is a list of modules to expose via any type of RPC (HTTP, IPC, in-proc)
APIModules = "eth,net,web3,shh,shhext"
// WSHost is a host interface for the websocket RPC server
WSHost = "localhost"
// SendTransactionMethodName defines the name for a giving transaction.
SendTransactionMethodName = "eth_sendTransaction"
@ -44,9 +41,6 @@ const (
// PersonalSignMethodName defines the name for `personal.sign` API.
PersonalSignMethodName = "personal_sign"
// WSPort is a WS-RPC port (replaced in unit tests)
WSPort = 8546
// MaxPeers is the maximum number of global peers
MaxPeers = 25

View File

@ -65,8 +65,8 @@ func (s *PeersTestSuite) TestStaticPeersReconnect() {
// both on rinkeby and ropsten we can expect atleast 2 peers connected
expectedPeersCount := 2
events := make(chan *p2p.PeerEvent, 10)
node, err := s.backend.StatusNode().GethNode()
s.Require().NoError(err)
node := s.backend.StatusNode().GethNode()
s.Require().NotNil(node)
subscription := node.Server().SubscribeEvents(events)
defer subscription.Unsubscribe()

View File

@ -219,8 +219,8 @@ func (s *AccountsTestSuite) TestSelectedAccountOnRestart() {
s.NoError(s.Backend.SelectAccount(address2, TestConfig.Account1.Password))
// stop node (and all of its sub-protocols)
nodeConfig, err := s.Backend.StatusNode().Config()
s.NoError(err)
nodeConfig := s.Backend.StatusNode().Config()
s.NotNil(nodeConfig)
preservedNodeConfig := *nodeConfig
s.NoError(s.Backend.StopNode())

View File

@ -46,16 +46,16 @@ func (s *ManagerTestSuite) TestReferencesWithoutStartedNode() {
{
"non-null manager, no running node, get NodeConfig",
func() (interface{}, error) {
return s.StatusNode.Config()
return s.StatusNode.Config(), nil
},
node.ErrNoRunningNode,
nil,
},
{
"non-null manager, no running node, get Node",
func() (interface{}, error) {
return s.StatusNode.GethNode()
return s.StatusNode.GethNode(), nil
},
node.ErrNoRunningNode,
nil,
},
{
"non-null manager, no running node, get LES",
@ -76,14 +76,14 @@ func (s *ManagerTestSuite) TestReferencesWithoutStartedNode() {
func() (interface{}, error) {
return s.StatusNode.AccountManager()
},
node.ErrNoRunningNode,
node.ErrNoGethNode,
},
{
"non-null manager, no running node, get AccountKeyStore",
func() (interface{}, error) {
return s.StatusNode.AccountKeyStore()
},
node.ErrNoRunningNode,
node.ErrNoGethNode,
},
{
"non-null manager, no running node, get RPC Client",
@ -116,14 +116,14 @@ func (s *ManagerTestSuite) TestReferencesWithStartedNode() {
{
"node is running, get NodeConfig",
func() (interface{}, error) {
return s.StatusNode.Config()
return s.StatusNode.Config(), nil
},
&params.NodeConfig{},
},
{
"node is running, get Node",
func() (interface{}, error) {
return s.StatusNode.GethNode()
return s.StatusNode.GethNode(), nil
},
&gethnode.Node{},
},
@ -188,7 +188,7 @@ func (s *ManagerTestSuite) TestNodeStartStop() {
s.True(s.StatusNode.IsRunning())
// try starting another node (w/o stopping the previously started node)
s.Equal(node.ErrNodeExists, s.StatusNode.Start(nodeConfig))
s.Equal(node.ErrNodeRunning, s.StatusNode.Start(nodeConfig))
// now stop node
time.Sleep(100 * time.Millisecond) //https://github.com/status-im/status-go/issues/429#issuecomment-339663163

View File

@ -40,7 +40,6 @@ func (s *RPCTestSuite) TestCallRPC() {
s.NoError(err)
nodeConfig.IPCEnabled = false
nodeConfig.WSEnabled = false
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
if upstreamEnabled {

View File

@ -29,9 +29,9 @@ func WithDataDir(path string) TestNodeOption {
// FirstBlockHash validates Attach operation for the StatusNode.
func FirstBlockHash(statusNode *node.StatusNode) (string, error) {
// obtain RPC client for running node
runningNode, err := statusNode.GethNode()
if err != nil {
return "", err
runningNode := statusNode.GethNode()
if runningNode == nil {
return "", node.ErrNoGethNode
}
rpcClient, err := runningNode.Attach()

View File

@ -43,10 +43,10 @@ func (s *WhisperExtensionSuite) SetupTest() {
}
func (s *WhisperExtensionSuite) TestSentSignal() {
node1, err := s.nodes[0].GethNode()
s.NoError(err)
node2, err := s.nodes[1].GethNode()
s.NoError(err)
node1 := s.nodes[0].GethNode()
s.NotNil(node1)
node2 := s.nodes[1].GethNode()
s.NotNil(node2)
node1.Server().AddPeer(node2.Server().Self())
confirmed := make(chan common.Hash, 1)
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
@ -125,8 +125,8 @@ func (s *WhisperExtensionSuite) TestExpiredSignal() {
func (s *WhisperExtensionSuite) TearDown() {
for _, n := range s.nodes {
cfg, err := n.Config()
s.NoError(err)
cfg := n.Config()
s.NotNil(cfg)
s.NoError(n.Stop())
s.NoError(os.Remove(cfg.DataDir))
}

View File

@ -28,17 +28,18 @@ func TestWhisperMailboxTestSuite(t *testing.T) {
}
func (s *WhisperMailboxSuite) TestRequestMessageFromMailboxAsync() {
var err error
// Start mailbox and status node.
mailboxBackend, stop := s.startMailboxBackend()
defer stop()
mailboxNode, err := mailboxBackend.StatusNode().GethNode()
s.Require().NoError(err)
s.Require().True(mailboxBackend.IsNodeRunning())
mailboxNode := mailboxBackend.StatusNode().GethNode()
mailboxEnode := mailboxNode.Server().NodeInfo().Enode
sender, stop := s.startBackend("sender")
defer stop()
node, err := sender.StatusNode().GethNode()
s.Require().NoError(err)
s.Require().True(sender.IsNodeRunning())
node := sender.StatusNode().GethNode()
s.Require().NotEqual(mailboxEnode, node.Server().NodeInfo().Enode)
@ -124,6 +125,8 @@ func (s *WhisperMailboxSuite) TestRequestMessageFromMailboxAsync() {
}
func (s *WhisperMailboxSuite) TestRequestMessagesInGroupChat() {
var err error
// Start mailbox, alice, bob, charlie node.
mailboxBackend, stop := s.startMailboxBackend()
defer stop()
@ -138,8 +141,8 @@ func (s *WhisperMailboxSuite) TestRequestMessagesInGroupChat() {
defer stop()
// Add mailbox to static peers.
mailboxNode, err := mailboxBackend.StatusNode().GethNode()
s.Require().NoError(err)
s.Require().True(mailboxBackend.IsNodeRunning())
mailboxNode := mailboxBackend.StatusNode().GethNode()
mailboxEnode := mailboxNode.Server().NodeInfo().Enode
err = aliceBackend.StatusNode().AddPeer(mailboxEnode)

View File

@ -167,8 +167,8 @@ func (s *WhisperTestSuite) TestSelectedAccountOnRestart() {
s.False(whisperService.HasKeyPair(pubKey1), "identity should be removed, but it is still present in whisper")
// stop node (and all of its sub-protocols)
nodeConfig, err := s.Backend.StatusNode().Config()
s.NoError(err)
nodeConfig := s.Backend.StatusNode().Config()
s.NotNil(nodeConfig)
preservedNodeConfig := *nodeConfig
s.NoError(s.Backend.StopNode())