aboutsummaryrefslogtreecommitdiffstats
path: root/node/node.go
diff options
context:
space:
mode:
Diffstat (limited to 'node/node.go')
-rw-r--r--node/node.go252
1 files changed, 252 insertions, 0 deletions
diff --git a/node/node.go b/node/node.go
new file mode 100644
index 000000000..ad4e69414
--- /dev/null
+++ b/node/node.go
@@ -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
+}