aboutsummaryrefslogtreecommitdiffstats
path: root/node
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2016-08-18 19:28:17 +0800
committerFelix Lange <fjl@twurst.com>2016-09-16 21:24:31 +0800
commiteeb322ae649c4a1a32430cdddfffed70f509181e (patch)
tree35622201208afb98665743d9bcf88883058e772a /node
parent52ede09b172094f8fd85f8b10e7d0578059353fb (diff)
downloadgo-tangerine-eeb322ae649c4a1a32430cdddfffed70f509181e.tar.gz
go-tangerine-eeb322ae649c4a1a32430cdddfffed70f509181e.tar.zst
go-tangerine-eeb322ae649c4a1a32430cdddfffed70f509181e.zip
node: ensure datadir can be co-inhabited by different instances
This change ensures that nodes started with different Name but same DataDir values don't use the same nodekey and IPC socket.
Diffstat (limited to 'node')
-rw-r--r--node/api.go18
-rw-r--r--node/config.go129
-rw-r--r--node/config_test.go42
-rw-r--r--node/doc.go90
-rw-r--r--node/node.go175
-rw-r--r--node/service.go19
-rw-r--r--node/service_test.go6
7 files changed, 350 insertions, 129 deletions
diff --git a/node/api.go b/node/api.go
index 3523874ab..2942705d9 100644
--- a/node/api.go
+++ b/node/api.go
@@ -85,16 +85,16 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *st
if host == nil {
h := common.DefaultHTTPHost
- if api.node.httpHost != "" {
- h = api.node.httpHost
+ if api.node.config.HTTPHost != "" {
+ h = api.node.config.HTTPHost
}
host = &h
}
if port == nil {
- port = rpc.NewHexNumber(api.node.httpPort)
+ port = rpc.NewHexNumber(api.node.config.HTTPPort)
}
if cors == nil {
- cors = &api.node.httpCors
+ cors = &api.node.config.HTTPCors
}
modules := api.node.httpWhitelist
@@ -134,19 +134,19 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOr
if host == nil {
h := common.DefaultWSHost
- if api.node.wsHost != "" {
- h = api.node.wsHost
+ if api.node.config.WSHost != "" {
+ h = api.node.config.WSHost
}
host = &h
}
if port == nil {
- port = rpc.NewHexNumber(api.node.wsPort)
+ port = rpc.NewHexNumber(api.node.config.WSPort)
}
if allowedOrigins == nil {
- allowedOrigins = &api.node.wsOrigins
+ allowedOrigins = &api.node.config.WSOrigins
}
- modules := api.node.wsWhitelist
+ modules := api.node.config.WSModules
if apis != nil {
modules = nil
for _, m := range strings.Split(*apis, ",") {
diff --git a/node/config.go b/node/config.go
index 432da7015..4a18432c7 100644
--- a/node/config.go
+++ b/node/config.go
@@ -18,7 +18,6 @@ package node
import (
"crypto/ecdsa"
- "encoding/json"
"fmt"
"io/ioutil"
"net"
@@ -48,6 +47,18 @@ var (
// P2P network layer of a protocol stack. These values can be further extended by
// all registered services.
type Config struct {
+ // Name sets the instance name of the node. It must not contain the / character and is
+ // used in the devp2p node identifier. The instance name of geth is "geth". If no
+ // value is specified, the basename of the current executable is used.
+ Name string
+
+ // UserIdent, if set, is used as an additional component in the devp2p node identifier.
+ UserIdent string
+
+ // Version should be set to the version number of the program. It is used
+ // in the devp2p node identifier.
+ Version string
+
// DataDir is the file system folder the node should use for any data storage
// requirements. The configured data directory will not be directly shared with
// registered services, instead those can use utility methods to create/access
@@ -80,10 +91,6 @@ type Config struct {
// needed.
PrivateKey *ecdsa.PrivateKey
- // Name sets the node name of this server. Use common.MakeName to create a name
- // that follows existing conventions.
- Name string
-
// NoDiscovery specifies whether the peer discovery mechanism should be started
// or not. Disabling is usually useful for protocol debugging (manual topology).
NoDiscovery bool
@@ -178,9 +185,23 @@ func (c *Config) IPCEndpoint() string {
return c.IPCPath
}
+// NodeDB returns the path to the discovery node database.
+func (c *Config) NodeDB() string {
+ if c.DataDir == "" {
+ return "" // ephemeral
+ }
+ return c.resolvePath("nodes")
+}
+
// DefaultIPCEndpoint returns the IPC path used by default.
-func DefaultIPCEndpoint() string {
- config := &Config{DataDir: common.DefaultDataDir(), IPCPath: common.DefaultIPCSocket}
+func DefaultIPCEndpoint(clientIdentifier string) string {
+ if clientIdentifier == "" {
+ clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
+ if clientIdentifier == "" {
+ panic("empty executable name")
+ }
+ }
+ config := &Config{DataDir: common.DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"}
return config.IPCEndpoint()
}
@@ -214,15 +235,76 @@ func DefaultWSEndpoint() string {
return config.WSEndpoint()
}
+// NodeName returns the devp2p node identifier.
+func (c *Config) NodeName() string {
+ name := c.name()
+ // Backwards compatibility: previous versions used title-cased "Geth", keep that.
+ if name == "geth" || name == "geth-testnet" {
+ name = "Geth"
+ }
+ if c.UserIdent != "" {
+ name += "/" + c.UserIdent
+ }
+ if c.Version != "" {
+ name += "/v" + c.Version
+ }
+ name += "/" + runtime.GOOS
+ name += "/" + runtime.Version()
+ return name
+}
+
+func (c *Config) name() string {
+ if c.Name == "" {
+ progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
+ if progname == "" {
+ panic("empty executable name, set Config.Name")
+ }
+ return progname
+ }
+ return c.Name
+}
+
+// These resources are resolved differently for the "geth" and "geth-testnet" instances.
+var isOldGethResource = map[string]bool{
+ "chaindata": true,
+ "nodes": true,
+ "nodekey": true,
+ "static-nodes.json": true,
+ "trusted-nodes.json": true,
+}
+
+// resolvePath resolves path in the instance directory.
+func (c *Config) resolvePath(path string) string {
+ if filepath.IsAbs(path) {
+ return path
+ }
+ if c.DataDir == "" {
+ return ""
+ }
+ // Backwards-compatibility: ensure that data directory files created
+ // by geth 1.4 are used if they exist.
+ if c.name() == "geth" && isOldGethResource[path] {
+ oldpath := ""
+ if c.Name == "geth" {
+ oldpath = filepath.Join(c.DataDir, path)
+ }
+ if oldpath != "" && common.FileExist(oldpath) {
+ // TODO: print warning
+ return oldpath
+ }
+ }
+ return filepath.Join(c.DataDir, c.name(), path)
+}
+
// NodeKey retrieves the currently configured private key of the node, checking
// first any manually set key, falling back to the one found in the configured
// data folder. If no key can be found, a new one is generated.
func (c *Config) NodeKey() *ecdsa.PrivateKey {
- // Use any specifically configured key
+ // Use any specifically configured key.
if c.PrivateKey != nil {
return c.PrivateKey
}
- // Generate ephemeral key if no datadir is being used
+ // Generate ephemeral key if no datadir is being used.
if c.DataDir == "" {
key, err := crypto.GenerateKey()
if err != nil {
@@ -230,16 +312,22 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey {
}
return key
}
- // Fall back to persistent key from the data directory
- keyfile := filepath.Join(c.DataDir, datadirPrivateKey)
+
+ keyfile := c.resolvePath(datadirPrivateKey)
if key, err := crypto.LoadECDSA(keyfile); err == nil {
return key
}
- // No persistent key found, generate and store a new one
+ // No persistent key found, generate and store a new one.
key, err := crypto.GenerateKey()
if err != nil {
glog.Fatalf("Failed to generate node key: %v", err)
}
+ instanceDir := filepath.Join(c.DataDir, c.name())
+ if err := os.MkdirAll(instanceDir, 0700); err != nil {
+ glog.V(logger.Error).Infof("Failed to persist node key: %v", err)
+ return key
+ }
+ keyfile = filepath.Join(instanceDir, datadirPrivateKey)
if err := crypto.SaveECDSA(keyfile, key); err != nil {
glog.V(logger.Error).Infof("Failed to persist node key: %v", err)
}
@@ -248,12 +336,12 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey {
// StaticNodes returns a list of node enode URLs configured as static nodes.
func (c *Config) StaticNodes() []*discover.Node {
- return c.parsePersistentNodes(datadirStaticNodes)
+ return c.parsePersistentNodes(c.resolvePath(datadirStaticNodes))
}
// TrusterNodes returns a list of node enode URLs configured as trusted nodes.
func (c *Config) TrusterNodes() []*discover.Node {
- return c.parsePersistentNodes(datadirTrustedNodes)
+ return c.parsePersistentNodes(c.resolvePath(datadirTrustedNodes))
}
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
@@ -267,15 +355,10 @@ func (c *Config) parsePersistentNodes(file string) []*discover.Node {
if _, err := os.Stat(path); err != nil {
return nil
}
- // Load the nodes from the config file
- blob, err := ioutil.ReadFile(path)
- if err != nil {
- glog.V(logger.Error).Infof("Failed to access nodes: %v", err)
- return nil
- }
- nodelist := []string{}
- if err := json.Unmarshal(blob, &nodelist); err != nil {
- glog.V(logger.Error).Infof("Failed to load nodes: %v", err)
+ // Load the nodes from the config file.
+ var nodelist []string
+ if err := common.LoadJSON(path, &nodelist); err != nil {
+ glog.V(logger.Error).Infof("Can't load node file %s: %v", path, err)
return nil
}
// Interpret the list as a discovery node array
diff --git a/node/config_test.go b/node/config_test.go
index 45a54d184..b258d2a8b 100644
--- a/node/config_test.go
+++ b/node/config_test.go
@@ -96,57 +96,55 @@ func TestIPCPathResolution(t *testing.T) {
// ephemeral.
func TestNodeKeyPersistency(t *testing.T) {
// Create a temporary folder and make sure no key is present
- dir, err := ioutil.TempDir("", "")
+ dir, err := ioutil.TempDir("", "node-test")
if err != nil {
t.Fatalf("failed to create temporary data directory: %v", err)
}
defer os.RemoveAll(dir)
- if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil {
- t.Fatalf("non-created node key already exists")
- }
+ keyfile := filepath.Join(dir, "unit-test", datadirPrivateKey)
+
// Configure a node with a preset key and ensure it's not persisted
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("failed to generate one-shot node key: %v", err)
}
- if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil {
- t.Fatalf("failed to create empty stack: %v", err)
- }
- if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil {
+ config := &Config{Name: "unit-test", DataDir: dir, PrivateKey: key}
+ config.NodeKey()
+ if _, err := os.Stat(filepath.Join(keyfile)); err == nil {
t.Fatalf("one-shot node key persisted to data directory")
}
+
// Configure a node with no preset key and ensure it is persisted this time
- if _, err := New(&Config{DataDir: dir}); err != nil {
- t.Fatalf("failed to create newly keyed stack: %v", err)
- }
- if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil {
+ config = &Config{Name: "unit-test", DataDir: dir}
+ config.NodeKey()
+ if _, err := os.Stat(keyfile); err != nil {
t.Fatalf("node key not persisted to data directory: %v", err)
}
- key, err = crypto.LoadECDSA(filepath.Join(dir, datadirPrivateKey))
+ key, err = crypto.LoadECDSA(keyfile)
if err != nil {
t.Fatalf("failed to load freshly persisted node key: %v", err)
}
- blob1, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey))
+ blob1, err := ioutil.ReadFile(keyfile)
if err != nil {
t.Fatalf("failed to read freshly persisted node key: %v", err)
}
+
// Configure a new node and ensure the previously persisted key is loaded
- if _, err := New(&Config{DataDir: dir}); err != nil {
- t.Fatalf("failed to create previously keyed stack: %v", err)
- }
- blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey))
+ config = &Config{Name: "unit-test", DataDir: dir}
+ config.NodeKey()
+ blob2, err := ioutil.ReadFile(filepath.Join(keyfile))
if err != nil {
t.Fatalf("failed to read previously persisted node key: %v", err)
}
if bytes.Compare(blob1, blob2) != 0 {
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1)
}
+
// Configure ephemeral node and ensure no key is dumped locally
- if _, err := New(&Config{DataDir: ""}); err != nil {
- t.Fatalf("failed to create ephemeral stack: %v", err)
- }
- if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil {
+ config = &Config{Name: "unit-test", DataDir: ""}
+ config.NodeKey()
+ if _, err := os.Stat(filepath.Join(".", "unit-test", datadirPrivateKey)); err == nil {
t.Fatalf("ephemeral node key persisted to disk")
}
}
diff --git a/node/doc.go b/node/doc.go
new file mode 100644
index 000000000..f009e6f85
--- /dev/null
+++ b/node/doc.go
@@ -0,0 +1,90 @@
+// Copyright 2016 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 sets up multi-protocol Ethereum nodes.
+
+In the model exposed by this package, a node is a collection of services which use shared
+resources to provide RPC APIs. Services can also offer devp2p protocols, which are wired
+up to the devp2p network when the node instance is started.
+
+
+Resources Managed By Node
+
+All file-system resources used by a node instance are located in a directory called the
+data directory. The location of each resource can be overridden through additional node
+configuration. The data directory is optional. If it is not set and the location of a
+resource is otherwise unspecified, package node will create the resource in memory.
+
+To access to the devp2p network, Node configures and starts p2p.Server. Each host on the
+devp2p network has a unique identifier, the node key. The Node instance persists this key
+across restarts. Node also loads static and trusted node lists and ensures that knowledge
+about other hosts is persisted.
+
+JSON-RPC servers which run HTTP, WebSocket or IPC can be started on a Node. RPC modules
+offered by registered services will be offered on those endpoints. Users can restrict any
+endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3"
+modules.
+
+Service implementations can open LevelDB databases through the service context. Package
+node chooses the file system location of each database. If the node is configured to run
+without a data directory, databases are opened in memory instead.
+
+Node also creates the shared store of encrypted Ethereum account keys. Services can access
+the account manager through the service context.
+
+
+Sharing Data Directory Among Instances
+
+Multiple node instances can share a single data directory if they have distinct instance
+names (set through the Name config option). Sharing behaviour depends on the type of
+resource.
+
+devp2p-related resources (node key, static/trusted node lists, known hosts database) are
+stored in a directory with the same name as the instance. Thus, multiple node instances
+using the same data directory will store this information in different subdirectories of
+the data directory.
+
+LevelDB databases are also stored within the instance subdirectory. If multiple node
+instances use the same data directory, openening the databases with identical names will
+create one database for each instance.
+
+The account key store is shared among all node instances using the same data directory
+unless its location is changed through the KeyStoreDir configuration option.
+
+
+Data Directory Sharing Example
+
+In this exanple, two node instances named A and B are started with the same data
+directory. Mode instance A opens the database "db", node instance B opens the databases
+"db" and "db-2". The following files will be created in the data directory:
+
+ data-directory/
+ A/
+ nodekey -- devp2p node key of instance A
+ nodes/ -- devp2p discovery knowledge database of instance A
+ db/ -- LevelDB content for "db"
+ A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A
+ B/
+ nodekey -- devp2p node key of node B
+ nodes/ -- devp2p discovery knowledge database of instance B
+ static-nodes.json -- devp2p static node list of instance B
+ db/ -- LevelDB content for "db"
+ db-2/ -- LevelDB content for "db-2"
+ B.ipc -- JSON-RPC UNIX domain socket endpoint of instance A
+ keystore/ -- account key store, used by both instances
+*/
+package node
diff --git a/node/node.go b/node/node.go
index f3be2f763..41c9eb27f 100644
--- a/node/node.go
+++ b/node/node.go
@@ -14,7 +14,6 @@
// 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 (
@@ -23,16 +22,19 @@ import (
"os"
"path/filepath"
"reflect"
+ "strings"
"sync"
"syscall"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
+ "github.com/syndtr/goleveldb/leveldb/storage"
)
var (
@@ -44,14 +46,14 @@ var (
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
)
-// Node represents a P2P node into which arbitrary (uniquely typed) services might
-// be registered.
+// Node is a container on which services can be registered.
type Node struct {
- datadir string // Path to the currently used data directory
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
+ config *Config
+ accman *accounts.Manager
- accman *accounts.Manager
- ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop
+ ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop
+ instanceDirLock storage.Storage // prevents concurrent use of instance directory
serverConfig p2p.Config
server *p2p.Server // Currently running P2P networking layer
@@ -66,21 +68,14 @@ type Node struct {
ipcListener net.Listener // IPC RPC listener socket to serve API requests
ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
- httpHost string // HTTP hostname
- httpPort int // HTTP post
httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
- httpCors string // HTTP RPC Cross-Origin Resource Sharing header
httpListener net.Listener // HTTP RPC listener socket to server API requests
httpHandler *rpc.Server // HTTP RPC request handler to process the API requests
- wsHost string // Websocket host
- wsPort int // Websocket post
- wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
- wsWhitelist []string // Websocket RPC modules to allow through this endpoint
- wsOrigins string // Websocket RPC allowed origin domains
- wsListener net.Listener // Websocket RPC listener socket to server API requests
- wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
+ wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
+ wsListener net.Listener // Websocket RPC listener socket to server API requests
+ wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
stop chan struct{} // Channel to wait for termination notifications
lock sync.RWMutex
@@ -88,54 +83,45 @@ type Node struct {
// 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
+ // Copy config and resolve the datadir so future changes to the current
+ // working directory don't affect the node.
+ confCopy := *conf
+ conf = &confCopy
if conf.DataDir != "" {
- if err := os.MkdirAll(conf.DataDir, 0700); err != nil {
+ absdatadir, err := filepath.Abs(conf.DataDir)
+ if err != nil {
return nil, err
}
+ conf.DataDir = absdatadir
+ }
+ // Ensure that the instance name doesn't cause weird conflicts with
+ // other files in the data directory.
+ if strings.ContainsAny(conf.Name, `/\`) {
+ return nil, errors.New(`Config.Name must not contain '/' or '\'`)
+ }
+ if conf.Name == datadirDefaultKeyStore {
+ return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`)
}
+ if strings.HasSuffix(conf.Name, ".ipc") {
+ return nil, errors.New(`Config.Name cannot end in ".ipc"`)
+ }
+ // Ensure that the AccountManager method works before the node has started.
+ // We rely on this in cmd/geth.
am, ephemeralKeystore, err := makeAccountManager(conf)
if err != nil {
return nil, err
}
-
- // Assemble the networking layer and the node itself
- nodeDbPath := ""
- if conf.DataDir != "" {
- nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase)
- }
+ // Note: any interaction with Config that would create/touch files
+ // in the data directory or instance directory is delayed until Start.
return &Node{
- datadir: conf.DataDir,
accman: am,
ephemeralKeystore: ephemeralKeystore,
- serverConfig: p2p.Config{
- 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,
- },
- serviceFuncs: []ServiceConstructor{},
- ipcEndpoint: conf.IPCEndpoint(),
- httpHost: conf.HTTPHost,
- httpPort: conf.HTTPPort,
- httpEndpoint: conf.HTTPEndpoint(),
- httpWhitelist: conf.HTTPModules,
- httpCors: conf.HTTPCors,
- wsHost: conf.WSHost,
- wsPort: conf.WSPort,
- wsEndpoint: conf.WSEndpoint(),
- wsWhitelist: conf.WSModules,
- wsOrigins: conf.WSOrigins,
- eventmux: new(event.TypeMux),
+ config: conf,
+ serviceFuncs: []ServiceConstructor{},
+ ipcEndpoint: conf.IPCEndpoint(),
+ httpEndpoint: conf.HTTPEndpoint(),
+ wsEndpoint: conf.WSEndpoint(),
+ eventmux: new(event.TypeMux),
}, nil
}
@@ -161,13 +147,36 @@ func (n *Node) Start() error {
if n.server != nil {
return ErrNodeRunning
}
- // Otherwise copy and specialize the P2P configuration
+ if err := n.openDataDir(); err != nil {
+ return err
+ }
+
+ // Initialize the p2p server. This creates the node key and
+ // discovery databases.
+ n.serverConfig = p2p.Config{
+ PrivateKey: n.config.NodeKey(),
+ Name: n.config.NodeName(),
+ Discovery: !n.config.NoDiscovery,
+ BootstrapNodes: n.config.BootstrapNodes,
+ StaticNodes: n.config.StaticNodes(),
+ TrustedNodes: n.config.TrusterNodes(),
+ NodeDatabase: n.config.NodeDB(),
+ ListenAddr: n.config.ListenAddr,
+ NAT: n.config.NAT,
+ Dialer: n.config.Dialer,
+ NoDial: n.config.NoDial,
+ MaxPeers: n.config.MaxPeers,
+ MaxPendingPeers: n.config.MaxPendingPeers,
+ }
running := &p2p.Server{Config: n.serverConfig}
+ glog.V(logger.Info).Infoln("instance:", n.serverConfig.Name)
+
+ // Otherwise copy and specialize the P2P configuration
services := make(map[reflect.Type]Service)
for _, constructor := range n.serviceFuncs {
// Create a new context for the particular service
ctx := &ServiceContext{
- datadir: n.datadir,
+ config: n.config,
services: make(map[reflect.Type]Service),
EventMux: n.eventmux,
AccountManager: n.accman,
@@ -227,6 +236,26 @@ func (n *Node) Start() error {
return nil
}
+func (n *Node) openDataDir() error {
+ if n.config.DataDir == "" {
+ return nil // ephemeral
+ }
+
+ instdir := filepath.Join(n.config.DataDir, n.config.name())
+ if err := os.MkdirAll(instdir, 0700); err != nil {
+ return err
+ }
+ // Try to open the instance directory as LevelDB storage. This creates a lock file
+ // which prevents concurrent use by another instance as well as accidental use of the
+ // instance directory as a database.
+ storage, err := storage.OpenFile(instdir, true)
+ if err != nil {
+ return err
+ }
+ n.instanceDirLock = storage
+ return nil
+}
+
// startRPC is a helper method to start all the various RPC endpoint during node
// startup. It's not meant to be called at any time afterwards as it makes certain
// assumptions about the state of the node.
@@ -244,12 +273,12 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
n.stopInProc()
return err
}
- if err := n.startHTTP(n.httpEndpoint, apis, n.httpWhitelist, n.httpCors); err != nil {
+ if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors); err != nil {
n.stopIPC()
n.stopInProc()
return err
}
- if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsOrigins); err != nil {
+ if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins); err != nil {
n.stopHTTP()
n.stopIPC()
n.stopInProc()
@@ -381,7 +410,6 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors
n.httpEndpoint = endpoint
n.httpListener = listener
n.httpHandler = handler
- n.httpCors = cors
return nil
}
@@ -436,7 +464,6 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig
n.wsEndpoint = endpoint
n.wsListener = listener
n.wsHandler = handler
- n.wsOrigins = wsOrigins
return nil
}
@@ -465,12 +492,12 @@ func (n *Node) Stop() error {
if n.server == nil {
return ErrNodeStopped
}
- // Otherwise terminate the API, all services and the P2P server too
+
+ // Terminate the API, services and the p2p server.
n.stopWS()
n.stopHTTP()
n.stopIPC()
n.rpcAPIs = nil
-
failure := &StopError{
Services: make(map[reflect.Type]error),
}
@@ -480,9 +507,16 @@ func (n *Node) Stop() error {
}
}
n.server.Stop()
-
n.services = nil
n.server = nil
+
+ // Release instance directory lock.
+ if n.instanceDirLock != nil {
+ n.instanceDirLock.Close()
+ n.instanceDirLock = nil
+ }
+
+ // unblock n.Wait
close(n.stop)
// Remove the keystore if it was created ephemerally.
@@ -566,7 +600,7 @@ func (n *Node) Service(service interface{}) error {
// DataDir retrieves the current datadir used by the protocol stack.
func (n *Node) DataDir() string {
- return n.datadir
+ return n.config.DataDir
}
// AccountManager retrieves the account manager used by the protocol stack.
@@ -595,6 +629,21 @@ func (n *Node) EventMux() *event.TypeMux {
return n.eventmux
}
+// OpenDatabase opens an existing database with the given name (or creates one if no
+// previous can be found) from within the node's instance directory. If the node is
+// ephemeral, a memory database is returned.
+func (n *Node) OpenDatabase(name string, cache, handles int) (ethdb.Database, error) {
+ if n.config.DataDir == "" {
+ return ethdb.NewMemDatabase()
+ }
+ return ethdb.NewLDBDatabase(n.config.resolvePath(name), cache, handles)
+}
+
+// ResolvePath returns the absolute path of a resource in the instance directory.
+func (n *Node) ResolvePath(x string) string {
+ return n.config.resolvePath(x)
+}
+
// apis returns the collection of RPC descriptors this node offers.
func (n *Node) apis() []rpc.API {
return []rpc.API{
diff --git a/node/service.go b/node/service.go
index 51531466b..1cd1fe808 100644
--- a/node/service.go
+++ b/node/service.go
@@ -17,7 +17,6 @@
package node
import (
- "path/filepath"
"reflect"
"github.com/ethereum/go-ethereum/accounts"
@@ -31,7 +30,7 @@ import (
// the protocol stack, that is passed to all constructors to be optionally used;
// as well as utility methods to operate on the service environment.
type ServiceContext struct {
- datadir string // Data directory for protocol persistence
+ config *Config
services map[reflect.Type]Service // Index of the already constructed services
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
AccountManager *accounts.Manager // Account manager created by the node.
@@ -41,10 +40,10 @@ type ServiceContext struct {
// if no previous can be found) from within the node's data directory. If the
// node is an ephemeral one, a memory database is returned.
func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int) (ethdb.Database, error) {
- if ctx.datadir == "" {
+ if ctx.config.DataDir == "" {
return ethdb.NewMemDatabase()
}
- return ethdb.NewLDBDatabase(filepath.Join(ctx.datadir, name), cache, handles)
+ return ethdb.NewLDBDatabase(ctx.config.resolvePath(name), cache, handles)
}
// Service retrieves a currently running service registered of a specific type.
@@ -64,11 +63,13 @@ type ServiceConstructor func(ctx *ServiceContext) (Service, error)
// Service is an individual protocol that can be registered into a node.
//
// Notes:
-// - Service life-cycle management is delegated to the node. The service is
-// allowed to initialize itself upon creation, but no goroutines should be
-// spun up outside of the Start method.
-// - Restart logic is not required as the node will create a fresh instance
-// every time a service is started.
+//
+// • Service life-cycle management is delegated to the node. The service is allowed to
+// initialize itself upon creation, but no goroutines should be spun up outside of the
+// Start method.
+//
+// • Restart logic is not required as the node will create a fresh instance
+// every time a service is started.
type Service interface {
// Protocols retrieves the P2P protocols the service wishes to start.
Protocols() []p2p.Protocol
diff --git a/node/service_test.go b/node/service_test.go
index 7bd94a52e..a7ae439e0 100644
--- a/node/service_test.go
+++ b/node/service_test.go
@@ -38,18 +38,18 @@ func TestContextDatabases(t *testing.T) {
t.Fatalf("non-created database already exists")
}
// Request the opening/creation of a database and ensure it persists to disk
- ctx := &ServiceContext{datadir: dir}
+ ctx := &ServiceContext{config: &Config{Name: "unit-test", DataDir: dir}}
db, err := ctx.OpenDatabase("persistent", 0, 0)
if err != nil {
t.Fatalf("failed to open persistent database: %v", err)
}
db.Close()
- if _, err := os.Stat(filepath.Join(dir, "persistent")); err != nil {
+ if _, err := os.Stat(filepath.Join(dir, "unit-test", "persistent")); err != nil {
t.Fatalf("persistent database doesn't exists: %v", err)
}
// Request th opening/creation of an ephemeral database and ensure it's not persisted
- ctx = &ServiceContext{datadir: ""}
+ ctx = &ServiceContext{config: &Config{DataDir: ""}}
db, err = ctx.OpenDatabase("ephemeral", 0, 0)
if err != nil {
t.Fatalf("failed to open ephemeral database: %v", err)