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.
This commit is contained in:
parent
52ede09b17
commit
eeb322ae64
@ -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...")
|
||||
start := time.Now()
|
||||
|
||||
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "chaindata"))
|
||||
|
||||
fmt.Printf("Removed in %v\n", time.Since(start))
|
||||
} else {
|
||||
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(dbdir)
|
||||
fmt.Printf("Removed in %v\n", time.Since(start))
|
||||
}
|
||||
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) {
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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{
|
||||
|
@ -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"},
|
||||
|
@ -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 := ð.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) {
|
||||
|
18
node/api.go
18
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, ",") {
|
||||
|
129
node/config.go
129
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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
90
node/doc.go
Normal file
90
node/doc.go
Normal file
@ -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
|
175
node/node.go
175
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{
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user