diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index 54984d6e01130869a08312eecaee71a576355535..553e5367c3b4c523b59e9293a41eae8f514f305f 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -79,7 +79,8 @@ func importChain(ctx *cli.Context) error {
 	if ctx.GlobalBool(utils.TestNetFlag.Name) {
 		state.StartingNonce = 1048576 // (2**20)
 	}
-	chain, chainDb := utils.MakeChain(ctx)
+	stack := makeFullNode(ctx)
+	chain, chainDb := utils.MakeChain(ctx, stack)
 	start := time.Now()
 	err := utils.ImportChain(chain, ctx.Args().First())
 	chainDb.Close()
@@ -94,7 +95,8 @@ func exportChain(ctx *cli.Context) error {
 	if len(ctx.Args()) < 1 {
 		utils.Fatalf("This command requires an argument.")
 	}
-	chain, _ := utils.MakeChain(ctx)
+	stack := makeFullNode(ctx)
+	chain, _ := utils.MakeChain(ctx, stack)
 	start := time.Now()
 
 	var err error
@@ -122,20 +124,25 @@ func exportChain(ctx *cli.Context) error {
 }
 
 func removeDB(ctx *cli.Context) error {
-	confirm, err := console.Stdin.PromptConfirm("Remove local database?")
-	if err != nil {
-		utils.Fatalf("%v", err)
+	stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
+	dbdir := stack.ResolvePath("chaindata")
+	if !common.FileExist(dbdir) {
+		fmt.Println(dbdir, "does not exist")
+		return nil
 	}
 
-	if confirm {
-		fmt.Println("Removing chaindata...")
+	fmt.Println(dbdir)
+	confirm, err := console.Stdin.PromptConfirm("Remove this database?")
+	switch {
+	case err != nil:
+		utils.Fatalf("%v", err)
+	case !confirm:
+		fmt.Println("Operation aborted")
+	default:
+		fmt.Println("Removing...")
 		start := time.Now()
-
-		os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "chaindata"))
-
+		os.RemoveAll(dbdir)
 		fmt.Printf("Removed in %v\n", time.Since(start))
-	} else {
-		fmt.Println("Operation aborted")
 	}
 	return nil
 }
@@ -143,7 +150,8 @@ func removeDB(ctx *cli.Context) error {
 func upgradeDB(ctx *cli.Context) error {
 	glog.Infoln("Upgrading blockchain database")
 
-	chain, chainDb := utils.MakeChain(ctx)
+	stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
+	chain, chainDb := utils.MakeChain(ctx, stack)
 	bcVersion := core.GetBlockChainVersion(chainDb)
 	if bcVersion == 0 {
 		bcVersion = core.BlockChainVersion
@@ -156,10 +164,12 @@ func upgradeDB(ctx *cli.Context) error {
 		utils.Fatalf("Unable to export chain for reimport %s", err)
 	}
 	chainDb.Close()
-	os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "chaindata"))
+	if dir := dbDirectory(chainDb); dir != "" {
+		os.RemoveAll(dir)
+	}
 
 	// Import the chain file.
-	chain, chainDb = utils.MakeChain(ctx)
+	chain, chainDb = utils.MakeChain(ctx, stack)
 	core.WriteBlockChainVersion(chainDb, core.BlockChainVersion)
 	err := utils.ImportChain(chain, exportFile)
 	chainDb.Close()
@@ -172,8 +182,17 @@ func upgradeDB(ctx *cli.Context) error {
 	return nil
 }
 
+func dbDirectory(db ethdb.Database) string {
+	ldb, ok := db.(*ethdb.LDBDatabase)
+	if !ok {
+		return ""
+	}
+	return ldb.Path()
+}
+
 func dump(ctx *cli.Context) error {
-	chain, chainDb := utils.MakeChain(ctx)
+	stack := makeFullNode(ctx)
+	chain, chainDb := utils.MakeChain(ctx, stack)
 	for _, arg := range ctx.Args() {
 		var block *types.Block
 		if hashish(arg) {
diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go
index 92d6f7f86ad6e5cc8869ad76e13b822ba99564ba..0662473037db51f2b56de12a21cf16bf5ae37eff 100644
--- a/cmd/geth/consolecmd.go
+++ b/cmd/geth/consolecmd.go
@@ -107,7 +107,7 @@ func remoteConsole(ctx *cli.Context) error {
 		utils.Fatalf("Unable to attach to remote geth: %v", err)
 	}
 	config := console.Config{
-		DataDir: utils.MustMakeDataDir(ctx),
+		DataDir: utils.MakeDataDir(ctx),
 		DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
 		Client:  client,
 		Preload: utils.MakeConsolePreloads(ctx),
@@ -135,7 +135,7 @@ func remoteConsole(ctx *cli.Context) error {
 // for "geth attach" and "geth monitor" with no argument.
 func dialRPC(endpoint string) (*rpc.Client, error) {
 	if endpoint == "" {
-		endpoint = node.DefaultIPCEndpoint()
+		endpoint = node.DefaultIPCEndpoint(clientIdentifier)
 	} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
 		// Backwards compatibility with geth < 1.5 which required
 		// these prefixes.
diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go
index 7058fb385c7b8f014195f72b680ce8b353897b9f..59730b17f57ed533e75ff75d991150094c5152ba 100644
--- a/cmd/geth/dao_test.go
+++ b/cmd/geth/dao_test.go
@@ -195,9 +195,9 @@ func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes
 		geth.cmd.Wait()
 	}
 	// Retrieve the DAO config flag from the database
-	path := filepath.Join(datadir, "chaindata")
+	path := filepath.Join(datadir, "geth", "chaindata")
 	if testnet && genesis == "" {
-		path = filepath.Join(datadir, "testnet", "chaindata")
+		path = filepath.Join(datadir, "testnet", "geth", "chaindata")
 	}
 	db, err := ethdb.NewLDBDatabase(path, 0, 0)
 	if err != nil {
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index a7b332d0ffd88c94ded5543d6fb69663adbb641b..65311ca418de5235d002082618da3b0931810f3e 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -36,7 +36,6 @@ import (
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/eth"
-	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/internal/debug"
 	"github.com/ethereum/go-ethereum/logger"
 	"github.com/ethereum/go-ethereum/logger/glog"
@@ -46,7 +45,7 @@ import (
 )
 
 const (
-	clientIdentifier = "Geth" // Client identifier to advertise over the network
+	clientIdentifier = "geth" // Client identifier to advertise over the network
 )
 
 var (
@@ -245,17 +244,15 @@ func initGenesis(ctx *cli.Context) error {
 		state.StartingNonce = 1048576 // (2**20)
 	}
 
-	chainDb, err := ethdb.NewLDBDatabase(filepath.Join(utils.MustMakeDataDir(ctx), "chaindata"), 0, 0)
-	if err != nil {
-		utils.Fatalf("could not open database: %v", err)
-	}
+	stack := makeFullNode(ctx)
+	chaindb := utils.MakeChainDatabase(ctx, stack)
 
 	genesisFile, err := os.Open(genesisPath)
 	if err != nil {
 		utils.Fatalf("failed to read genesis file: %v", err)
 	}
 
-	block, err := core.WriteGenesisBlock(chainDb, genesisFile)
+	block, err := core.WriteGenesisBlock(chaindb, genesisFile)
 	if err != nil {
 		utils.Fatalf("failed to write genesis block: %v", err)
 	}
@@ -296,9 +293,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
 // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
 // miner.
 func startNode(ctx *cli.Context, stack *node.Node) {
-	// Report geth version
-	glog.V(logger.Info).Infof("instance: Geth/%s/%s/%s\n", utils.Version, runtime.Version(), runtime.GOOS)
-
 	// Start up the node itself
 	utils.StartNode(stack)
 
@@ -379,7 +373,7 @@ func gpubench(ctx *cli.Context) error {
 }
 
 func version(c *cli.Context) error {
-	fmt.Println(clientIdentifier)
+	fmt.Println(strings.Title(clientIdentifier))
 	fmt.Println("Version:", utils.Version)
 	if gitCommit != "" {
 		fmt.Println("Git Commit:", gitCommit)
diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go
index d1490dce2faa357ad6b866298e36b66d5d313d92..b74315dabcd1c4d9d35e50ca4e12843ef71a0bb0 100644
--- a/cmd/geth/monitorcmd.go
+++ b/cmd/geth/monitorcmd.go
@@ -35,7 +35,7 @@ import (
 var (
 	monitorCommandAttachFlag = cli.StringFlag{
 		Name:  "attach",
-		Value: node.DefaultIPCEndpoint(),
+		Value: node.DefaultIPCEndpoint(clientIdentifier),
 		Usage: "API endpoint to attach to",
 	}
 	monitorCommandRowsFlag = cli.IntFlag{
diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go
index d267dbf585e76f7bff067fd0bfe66aff9684eaed..2c35ec9433dfe46555c2b6082f122a0d45a2be3d 100644
--- a/cmd/gethrpctest/main.go
+++ b/cmd/gethrpctest/main.go
@@ -88,7 +88,7 @@ func MakeSystemNode(privkey string, test *tests.BlockTest) (*node.Node, error) {
 	// Create a networkless protocol stack
 	stack, err := node.New(&node.Config{
 		UseLightweightKDF: true,
-		IPCPath:           node.DefaultIPCEndpoint(),
+		IPCPath:           node.DefaultIPCEndpoint(""),
 		HTTPHost:          common.DefaultHTTPHost,
 		HTTPPort:          common.DefaultHTTPPort,
 		HTTPModules:       []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"},
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 3ab556a8fc98f933119482a989953d504fc99f5d..47a8ebd4186cd16b25776c03a0af59dad62db900 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -396,13 +396,14 @@ var (
 	}
 )
 
-// MustMakeDataDir retrieves the currently requested data directory, terminating
+// MakeDataDir retrieves the currently requested data directory, terminating
 // if none (or the empty string) is specified. If the node is starting a testnet,
 // the a subdirectory of the specified datadir will be used.
-func MustMakeDataDir(ctx *cli.Context) string {
+func MakeDataDir(ctx *cli.Context) string {
 	if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
+		// TODO: choose a different location outside of the regular datadir.
 		if ctx.GlobalBool(TestNetFlag.Name) {
-			return filepath.Join(path, "/testnet")
+			return filepath.Join(path, "testnet")
 		}
 		return path
 	}
@@ -447,16 +448,16 @@ func MakeNodeKey(ctx *cli.Context) *ecdsa.PrivateKey {
 	return key
 }
 
-// MakeNodeName creates a node name from a base set and the command line flags.
-func MakeNodeName(client, version string, ctx *cli.Context) string {
-	name := common.MakeName(client, version)
+// makeNodeUserIdent creates the user identifier from CLI flags.
+func makeNodeUserIdent(ctx *cli.Context) string {
+	var comps []string
 	if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 {
-		name += "/" + identity
+		comps = append(comps, identity)
 	}
 	if ctx.GlobalBool(VMEnableJitFlag.Name) {
-		name += "/JIT"
+		comps = append(comps, "JIT")
 	}
-	return name
+	return strings.Join(comps, "/")
 }
 
 // MakeBootstrapNodes creates a list of bootstrap nodes from the command line
@@ -612,11 +613,13 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
 	}
 
 	config := &node.Config{
-		DataDir:           MustMakeDataDir(ctx),
+		DataDir:           MakeDataDir(ctx),
 		KeyStoreDir:       ctx.GlobalString(KeyStoreDirFlag.Name),
 		UseLightweightKDF: ctx.GlobalBool(LightKDFFlag.Name),
 		PrivateKey:        MakeNodeKey(ctx),
-		Name:              MakeNodeName(name, vsn, ctx),
+		Name:              name,
+		Version:           vsn,
+		UserIdent:         makeNodeUserIdent(ctx),
 		NoDiscovery:       ctx.GlobalBool(NoDiscoverFlag.Name),
 		BootstrapNodes:    MakeBootstrapNodes(ctx),
 		ListenAddr:        MakeListenAddress(ctx),
@@ -674,7 +677,7 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
 
 	ethConf := &eth.Config{
 		Etherbase:               MakeEtherbase(stack.AccountManager(), ctx),
-		ChainConfig:             MustMakeChainConfig(ctx),
+		ChainConfig:             MakeChainConfig(ctx, stack),
 		FastSync:                ctx.GlobalBool(FastSyncFlag.Name),
 		DatabaseCache:           ctx.GlobalInt(CacheFlag.Name),
 		DatabaseHandles:         MakeDatabaseHandles(),
@@ -748,16 +751,16 @@ func SetupNetwork(ctx *cli.Context) {
 	params.TargetGasLimit = common.String2Big(ctx.GlobalString(TargetGasLimitFlag.Name))
 }
 
-// MustMakeChainConfig reads the chain configuration from the database in ctx.Datadir.
-func MustMakeChainConfig(ctx *cli.Context) *core.ChainConfig {
-	db := MakeChainDatabase(ctx)
+// MakeChainConfig reads the chain configuration from the database in ctx.Datadir.
+func MakeChainConfig(ctx *cli.Context, stack *node.Node) *core.ChainConfig {
+	db := MakeChainDatabase(ctx, stack)
 	defer db.Close()
 
-	return MustMakeChainConfigFromDb(ctx, db)
+	return MakeChainConfigFromDb(ctx, db)
 }
 
-// MustMakeChainConfigFromDb reads the chain configuration from the given database.
-func MustMakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig {
+// MakeChainConfigFromDb reads the chain configuration from the given database.
+func MakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig {
 	// If the chain is already initialized, use any existing chain configs
 	config := new(core.ChainConfig)
 
@@ -800,14 +803,13 @@ func MustMakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainC
 }
 
 // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
-func MakeChainDatabase(ctx *cli.Context) ethdb.Database {
+func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
 	var (
-		datadir = MustMakeDataDir(ctx)
 		cache   = ctx.GlobalInt(CacheFlag.Name)
 		handles = MakeDatabaseHandles()
 	)
 
-	chainDb, err := ethdb.NewLDBDatabase(filepath.Join(datadir, "chaindata"), cache, handles)
+	chainDb, err := stack.OpenDatabase("chaindata", cache, handles)
 	if err != nil {
 		Fatalf("Could not open database: %v", err)
 	}
@@ -815,9 +817,9 @@ func MakeChainDatabase(ctx *cli.Context) ethdb.Database {
 }
 
 // MakeChain creates a chain manager from set command line flags.
-func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database) {
+func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chainDb ethdb.Database) {
 	var err error
-	chainDb = MakeChainDatabase(ctx)
+	chainDb = MakeChainDatabase(ctx, stack)
 
 	if ctx.GlobalBool(OlympicFlag.Name) {
 		_, err := core.WriteTestNetGenesisBlock(chainDb)
@@ -825,7 +827,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
 			glog.Fatalln(err)
 		}
 	}
-	chainConfig := MustMakeChainConfigFromDb(ctx, chainDb)
+	chainConfig := MakeChainConfigFromDb(ctx, chainDb)
 
 	pow := pow.PoW(core.FakePow{})
 	if !ctx.GlobalBool(FakePoWFlag.Name) {
diff --git a/node/api.go b/node/api.go
index 3523874ab8d8c4086fc3b15d77ee50bda91f9c11..2942705d95d0fecd90c96543e8f829b8ebbbd301 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 432da7015f74c443dab57415de2aea14621017ea..4a18432c7f810ee5356845a9734acc43acbb7cee 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 45a54d184ab29b1648cf85a81b0c2cd1708f3726..b258d2a8b8bb7abdffad8a350c661eb103f1564b 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 0000000000000000000000000000000000000000..f009e6f85d93fedca7c712b767c397790ef1de53
--- /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 f3be2f763fab0d1397c76253c26e3f2745b31680..41c9eb27f7fe4128191639efaafeee70a3af230b 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 51531466ba8a2cc26f5065ed45e297cfaea656d3..1cd1fe808e62c75493b39cff0c3fabb21e3bcc8f 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 7bd94a52e648f2958699ef4e9926ec992401ca02..a7ae439e0a908799d6c8f89c8185b820dcbea61c 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)