aboutsummaryrefslogtreecommitdiffstats
path: root/node/node.go
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/node.go
parent52ede09b172094f8fd85f8b10e7d0578059353fb (diff)
downloaddexon-eeb322ae649c4a1a32430cdddfffed70f509181e.tar.gz
dexon-eeb322ae649c4a1a32430cdddfffed70f509181e.tar.zst
dexon-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/node.go')
-rw-r--r--node/node.go175
1 files changed, 112 insertions, 63 deletions
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{