node: customizable protocol and service stacks
This commit is contained in:
252
node/node.go
Normal file
252
node/node.go
Normal file
@@ -0,0 +1,252 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package node represents the Ethereum protocol stack container.
|
||||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDatadirUsed = errors.New("datadir already used")
|
||||
ErrNodeStopped = errors.New("node not started")
|
||||
ErrNodeRunning = errors.New("node already running")
|
||||
ErrServiceUnknown = errors.New("service not registered")
|
||||
ErrServiceRegistered = errors.New("service already registered")
|
||||
|
||||
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
|
||||
)
|
||||
|
||||
// Node represents a P2P node into which arbitrary services might be registered.
|
||||
type Node struct {
|
||||
datadir string // Path to the currently used data directory
|
||||
config *p2p.Server // Configuration of the underlying P2P networking layer
|
||||
stack map[string]ServiceConstructor // Protocol stack registered into this node
|
||||
emux *event.TypeMux // Event multiplexer used between the services of a stack
|
||||
|
||||
running *p2p.Server // Currently running P2P networking layer
|
||||
services map[string]Service // Currently running services
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a new P2P node, ready for protocol registration.
|
||||
func New(conf *Config) (*Node, error) {
|
||||
// Ensure the data directory exists, failing if it cannot be created
|
||||
if conf.DataDir != "" {
|
||||
if err := os.MkdirAll(conf.DataDir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Assemble the networking layer and the node itself
|
||||
nodeDbPath := ""
|
||||
if conf.DataDir != "" {
|
||||
nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase)
|
||||
}
|
||||
return &Node{
|
||||
datadir: conf.DataDir,
|
||||
config: &p2p.Server{
|
||||
PrivateKey: conf.NodeKey(),
|
||||
Name: conf.Name,
|
||||
Discovery: !conf.NoDiscovery,
|
||||
BootstrapNodes: conf.BootstrapNodes,
|
||||
StaticNodes: conf.StaticNodes(),
|
||||
TrustedNodes: conf.TrusterNodes(),
|
||||
NodeDatabase: nodeDbPath,
|
||||
ListenAddr: conf.ListenAddr,
|
||||
NAT: conf.NAT,
|
||||
Dialer: conf.Dialer,
|
||||
NoDial: conf.NoDial,
|
||||
MaxPeers: conf.MaxPeers,
|
||||
MaxPendingPeers: conf.MaxPendingPeers,
|
||||
},
|
||||
stack: make(map[string]ServiceConstructor),
|
||||
emux: new(event.TypeMux),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Register injects a new service into the node's stack.
|
||||
func (n *Node) Register(id string, constructor ServiceConstructor) error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node is running or if the id is taken
|
||||
if n.running != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
if _, ok := n.stack[id]; ok {
|
||||
return ErrServiceRegistered
|
||||
}
|
||||
// Otherwise register the service and return
|
||||
n.stack[id] = constructor
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister removes a service from a node's stack. If the node is currently
|
||||
// running, an error will be returned.
|
||||
func (n *Node) Unregister(id string) error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node is running, or if the service is unknown
|
||||
if n.running != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
if _, ok := n.stack[id]; !ok {
|
||||
return ErrServiceUnknown
|
||||
}
|
||||
// Otherwise drop the service and return
|
||||
delete(n.stack, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start create a live P2P node and starts running it.
|
||||
func (n *Node) Start() error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node's already running
|
||||
if n.running != nil {
|
||||
return ErrNodeRunning
|
||||
}
|
||||
// Otherwise copy and specialize the P2P configuration
|
||||
running := new(p2p.Server)
|
||||
*running = *n.config
|
||||
|
||||
ctx := &ServiceContext{
|
||||
dataDir: n.datadir,
|
||||
EventMux: n.emux,
|
||||
}
|
||||
services := make(map[string]Service)
|
||||
for id, constructor := range n.stack {
|
||||
service, err := constructor(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
services[id] = service
|
||||
}
|
||||
// Gather the protocols and start the freshly assembled P2P server
|
||||
for _, service := range services {
|
||||
running.Protocols = append(running.Protocols, service.Protocols()...)
|
||||
}
|
||||
if err := running.Start(); err != nil {
|
||||
if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] {
|
||||
return ErrDatadirUsed
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Start each of the services
|
||||
started := []string{}
|
||||
for id, service := range services {
|
||||
// Start the next service, stopping all previous upon failure
|
||||
if err := service.Start(); err != nil {
|
||||
for _, id := range started {
|
||||
services[id].Stop()
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Mark the service started for potential cleanup
|
||||
started = append(started, id)
|
||||
}
|
||||
// Finish initializing the startup
|
||||
n.services = services
|
||||
n.running = running
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop terminates a running node along with all it's services. In the node was
|
||||
// not started, an error is returned.
|
||||
func (n *Node) Stop() error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
|
||||
// Short circuit if the node's not running
|
||||
if n.running == nil {
|
||||
return ErrNodeStopped
|
||||
}
|
||||
// Otherwise terminate all the services and the P2P server too
|
||||
failure := &StopError{
|
||||
Services: make(map[string]error),
|
||||
}
|
||||
for id, service := range n.services {
|
||||
if err := service.Stop(); err != nil {
|
||||
failure.Services[id] = err
|
||||
}
|
||||
}
|
||||
n.running.Stop()
|
||||
|
||||
n.services = nil
|
||||
n.running = nil
|
||||
|
||||
if len(failure.Services) > 0 {
|
||||
return failure
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart terminates a running node and boots up a new one in its place. If the
|
||||
// node isn't running, an error is returned.
|
||||
func (n *Node) Restart() error {
|
||||
if err := n.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server retrieves the currently running P2P network layer. This method is meant
|
||||
// only to inspect fields of the currently running server, life cycle management
|
||||
// should be left to this Node entity.
|
||||
func (n *Node) Server() *p2p.Server {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
return n.running
|
||||
}
|
||||
|
||||
// Service retrieves a currently running services registered under a given id.
|
||||
func (n *Node) Service(id string) Service {
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
|
||||
if n.services == nil {
|
||||
return nil
|
||||
}
|
||||
return n.services[id]
|
||||
}
|
||||
|
||||
// DataDir retrieves the current datadir used by the protocol stack.
|
||||
func (n *Node) DataDir() string {
|
||||
return n.datadir
|
||||
}
|
||||
|
||||
// EventMux retrieves the event multiplexer used by all the network services in
|
||||
// the current protocol stack.
|
||||
func (n *Node) EventMux() *event.TypeMux {
|
||||
return n.emux
|
||||
}
|
Reference in New Issue
Block a user