From f5c432bcab70ddc818db90ae410e07e6253585af Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 6 Sep 2016 00:26:54 +0200 Subject: [PATCH 1/4] p2p/nat: delay auto discovery until first use Port mapper auto discovery used to run immediately after parsing the --nat flag, giving it a slight performance boost. But this is becoming inconvenient because we create node.Node for all geth operations including account management and bare chain interaction. Delay autodiscovery until the first use instead, which avoids any network interaction until the node is actually started. --- p2p/nat/nat.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index 505a1fc77..42f615f36 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -197,11 +197,7 @@ type autodisc struct { func startautodisc(what string, doit func() Interface) Interface { // TODO: monitor network configuration and rerun doit when it changes. - ad := &autodisc{what: what, doit: doit} - // Start the auto discovery as early as possible so it is already - // in progress when the rest of the stack calls the methods. - go ad.wait() - return ad + return &autodisc{what: what, doit: doit} } func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { From 52ede09b172094f8fd85f8b10e7d0578059353fb Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 6 Sep 2016 00:31:30 +0200 Subject: [PATCH 2/4] ethdb: add accessor for database directory --- ethdb/database.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ethdb/database.go b/ethdb/database.go index f93731cfe..2e951927c 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -98,6 +98,11 @@ func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) { }, nil } +// Path returns the path to the database directory. +func (db *LDBDatabase) Path() string { + return db.fn +} + // Put puts the given key / value to the queue func (self *LDBDatabase) Put(key []byte, value []byte) error { // Measure the database put latency, if requested From eeb322ae649c4a1a32430cdddfffed70f509181e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 18 Aug 2016 13:28:17 +0200 Subject: [PATCH 3/4] 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. --- cmd/geth/chaincmd.go | 53 ++++++++---- cmd/geth/consolecmd.go | 4 +- cmd/geth/dao_test.go | 4 +- cmd/geth/main.go | 16 ++-- cmd/geth/monitorcmd.go | 2 +- cmd/gethrpctest/main.go | 2 +- cmd/utils/flags.go | 50 ++++++------ node/api.go | 18 ++--- node/config.go | 129 +++++++++++++++++++++++------ node/config_test.go | 42 +++++----- node/doc.go | 90 +++++++++++++++++++++ node/node.go | 175 +++++++++++++++++++++++++--------------- node/service.go | 19 ++--- node/service_test.go | 6 +- 14 files changed, 423 insertions(+), 187 deletions(-) create mode 100644 node/doc.go diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 54984d6e0..553e5367c 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...") - 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) { diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 92d6f7f86..066247303 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 7058fb385..59730b17f 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 a7b332d0f..65311ca41 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 d1490dce2..b74315dab 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 d267dbf58..2c35ec943 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 3ab556a8f..47a8ebd41 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 := ð.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 3523874ab..2942705d9 100644 --- a/node/api.go +++ b/node/api.go @@ -85,16 +85,16 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *st if host == nil { h := common.DefaultHTTPHost - if api.node.httpHost != "" { - h = api.node.httpHost + if api.node.config.HTTPHost != "" { + h = api.node.config.HTTPHost } host = &h } if port == nil { - port = rpc.NewHexNumber(api.node.httpPort) + port = rpc.NewHexNumber(api.node.config.HTTPPort) } if cors == nil { - cors = &api.node.httpCors + cors = &api.node.config.HTTPCors } modules := api.node.httpWhitelist @@ -134,19 +134,19 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOr if host == nil { h := common.DefaultWSHost - if api.node.wsHost != "" { - h = api.node.wsHost + if api.node.config.WSHost != "" { + h = api.node.config.WSHost } host = &h } if port == nil { - port = rpc.NewHexNumber(api.node.wsPort) + port = rpc.NewHexNumber(api.node.config.WSPort) } if allowedOrigins == nil { - allowedOrigins = &api.node.wsOrigins + allowedOrigins = &api.node.config.WSOrigins } - modules := api.node.wsWhitelist + modules := api.node.config.WSModules if apis != nil { modules = nil for _, m := range strings.Split(*apis, ",") { diff --git a/node/config.go b/node/config.go index 432da7015..4a18432c7 100644 --- a/node/config.go +++ b/node/config.go @@ -18,7 +18,6 @@ package node import ( "crypto/ecdsa" - "encoding/json" "fmt" "io/ioutil" "net" @@ -48,6 +47,18 @@ var ( // P2P network layer of a protocol stack. These values can be further extended by // all registered services. type Config struct { + // Name sets the instance name of the node. It must not contain the / character and is + // used in the devp2p node identifier. The instance name of geth is "geth". If no + // value is specified, the basename of the current executable is used. + Name string + + // UserIdent, if set, is used as an additional component in the devp2p node identifier. + UserIdent string + + // Version should be set to the version number of the program. It is used + // in the devp2p node identifier. + Version string + // DataDir is the file system folder the node should use for any data storage // requirements. The configured data directory will not be directly shared with // registered services, instead those can use utility methods to create/access @@ -80,10 +91,6 @@ type Config struct { // needed. PrivateKey *ecdsa.PrivateKey - // Name sets the node name of this server. Use common.MakeName to create a name - // that follows existing conventions. - Name string - // NoDiscovery specifies whether the peer discovery mechanism should be started // or not. Disabling is usually useful for protocol debugging (manual topology). NoDiscovery bool @@ -178,9 +185,23 @@ func (c *Config) IPCEndpoint() string { return c.IPCPath } +// NodeDB returns the path to the discovery node database. +func (c *Config) NodeDB() string { + if c.DataDir == "" { + return "" // ephemeral + } + return c.resolvePath("nodes") +} + // DefaultIPCEndpoint returns the IPC path used by default. -func DefaultIPCEndpoint() string { - config := &Config{DataDir: common.DefaultDataDir(), IPCPath: common.DefaultIPCSocket} +func DefaultIPCEndpoint(clientIdentifier string) string { + if clientIdentifier == "" { + clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") + if clientIdentifier == "" { + panic("empty executable name") + } + } + config := &Config{DataDir: common.DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} return config.IPCEndpoint() } @@ -214,15 +235,76 @@ func DefaultWSEndpoint() string { return config.WSEndpoint() } +// NodeName returns the devp2p node identifier. +func (c *Config) NodeName() string { + name := c.name() + // Backwards compatibility: previous versions used title-cased "Geth", keep that. + if name == "geth" || name == "geth-testnet" { + name = "Geth" + } + if c.UserIdent != "" { + name += "/" + c.UserIdent + } + if c.Version != "" { + name += "/v" + c.Version + } + name += "/" + runtime.GOOS + name += "/" + runtime.Version() + return name +} + +func (c *Config) name() string { + if c.Name == "" { + progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") + if progname == "" { + panic("empty executable name, set Config.Name") + } + return progname + } + return c.Name +} + +// These resources are resolved differently for the "geth" and "geth-testnet" instances. +var isOldGethResource = map[string]bool{ + "chaindata": true, + "nodes": true, + "nodekey": true, + "static-nodes.json": true, + "trusted-nodes.json": true, +} + +// resolvePath resolves path in the instance directory. +func (c *Config) resolvePath(path string) string { + if filepath.IsAbs(path) { + return path + } + if c.DataDir == "" { + return "" + } + // Backwards-compatibility: ensure that data directory files created + // by geth 1.4 are used if they exist. + if c.name() == "geth" && isOldGethResource[path] { + oldpath := "" + if c.Name == "geth" { + oldpath = filepath.Join(c.DataDir, path) + } + if oldpath != "" && common.FileExist(oldpath) { + // TODO: print warning + return oldpath + } + } + return filepath.Join(c.DataDir, c.name(), path) +} + // NodeKey retrieves the currently configured private key of the node, checking // first any manually set key, falling back to the one found in the configured // data folder. If no key can be found, a new one is generated. func (c *Config) NodeKey() *ecdsa.PrivateKey { - // Use any specifically configured key + // Use any specifically configured key. if c.PrivateKey != nil { return c.PrivateKey } - // Generate ephemeral key if no datadir is being used + // Generate ephemeral key if no datadir is being used. if c.DataDir == "" { key, err := crypto.GenerateKey() if err != nil { @@ -230,16 +312,22 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey { } return key } - // Fall back to persistent key from the data directory - keyfile := filepath.Join(c.DataDir, datadirPrivateKey) + + keyfile := c.resolvePath(datadirPrivateKey) if key, err := crypto.LoadECDSA(keyfile); err == nil { return key } - // No persistent key found, generate and store a new one + // No persistent key found, generate and store a new one. key, err := crypto.GenerateKey() if err != nil { glog.Fatalf("Failed to generate node key: %v", err) } + instanceDir := filepath.Join(c.DataDir, c.name()) + if err := os.MkdirAll(instanceDir, 0700); err != nil { + glog.V(logger.Error).Infof("Failed to persist node key: %v", err) + return key + } + keyfile = filepath.Join(instanceDir, datadirPrivateKey) if err := crypto.SaveECDSA(keyfile, key); err != nil { glog.V(logger.Error).Infof("Failed to persist node key: %v", err) } @@ -248,12 +336,12 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey { // StaticNodes returns a list of node enode URLs configured as static nodes. func (c *Config) StaticNodes() []*discover.Node { - return c.parsePersistentNodes(datadirStaticNodes) + return c.parsePersistentNodes(c.resolvePath(datadirStaticNodes)) } // TrusterNodes returns a list of node enode URLs configured as trusted nodes. func (c *Config) TrusterNodes() []*discover.Node { - return c.parsePersistentNodes(datadirTrustedNodes) + return c.parsePersistentNodes(c.resolvePath(datadirTrustedNodes)) } // parsePersistentNodes parses a list of discovery node URLs loaded from a .json @@ -267,15 +355,10 @@ func (c *Config) parsePersistentNodes(file string) []*discover.Node { if _, err := os.Stat(path); err != nil { return nil } - // Load the nodes from the config file - blob, err := ioutil.ReadFile(path) - if err != nil { - glog.V(logger.Error).Infof("Failed to access nodes: %v", err) - return nil - } - nodelist := []string{} - if err := json.Unmarshal(blob, &nodelist); err != nil { - glog.V(logger.Error).Infof("Failed to load nodes: %v", err) + // Load the nodes from the config file. + var nodelist []string + if err := common.LoadJSON(path, &nodelist); err != nil { + glog.V(logger.Error).Infof("Can't load node file %s: %v", path, err) return nil } // Interpret the list as a discovery node array diff --git a/node/config_test.go b/node/config_test.go index 45a54d184..b258d2a8b 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -96,57 +96,55 @@ func TestIPCPathResolution(t *testing.T) { // ephemeral. func TestNodeKeyPersistency(t *testing.T) { // Create a temporary folder and make sure no key is present - dir, err := ioutil.TempDir("", "") + dir, err := ioutil.TempDir("", "node-test") if err != nil { t.Fatalf("failed to create temporary data directory: %v", err) } defer os.RemoveAll(dir) - if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { - t.Fatalf("non-created node key already exists") - } + keyfile := filepath.Join(dir, "unit-test", datadirPrivateKey) + // Configure a node with a preset key and ensure it's not persisted key, err := crypto.GenerateKey() if err != nil { t.Fatalf("failed to generate one-shot node key: %v", err) } - if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil { - t.Fatalf("failed to create empty stack: %v", err) - } - if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { + config := &Config{Name: "unit-test", DataDir: dir, PrivateKey: key} + config.NodeKey() + if _, err := os.Stat(filepath.Join(keyfile)); err == nil { t.Fatalf("one-shot node key persisted to data directory") } + // Configure a node with no preset key and ensure it is persisted this time - if _, err := New(&Config{DataDir: dir}); err != nil { - t.Fatalf("failed to create newly keyed stack: %v", err) - } - if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil { + config = &Config{Name: "unit-test", DataDir: dir} + config.NodeKey() + if _, err := os.Stat(keyfile); err != nil { t.Fatalf("node key not persisted to data directory: %v", err) } - key, err = crypto.LoadECDSA(filepath.Join(dir, datadirPrivateKey)) + key, err = crypto.LoadECDSA(keyfile) if err != nil { t.Fatalf("failed to load freshly persisted node key: %v", err) } - blob1, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) + blob1, err := ioutil.ReadFile(keyfile) if err != nil { t.Fatalf("failed to read freshly persisted node key: %v", err) } + // Configure a new node and ensure the previously persisted key is loaded - if _, err := New(&Config{DataDir: dir}); err != nil { - t.Fatalf("failed to create previously keyed stack: %v", err) - } - blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) + config = &Config{Name: "unit-test", DataDir: dir} + config.NodeKey() + blob2, err := ioutil.ReadFile(filepath.Join(keyfile)) if err != nil { t.Fatalf("failed to read previously persisted node key: %v", err) } if bytes.Compare(blob1, blob2) != 0 { t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1) } + // Configure ephemeral node and ensure no key is dumped locally - if _, err := New(&Config{DataDir: ""}); err != nil { - t.Fatalf("failed to create ephemeral stack: %v", err) - } - if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil { + config = &Config{Name: "unit-test", DataDir: ""} + config.NodeKey() + if _, err := os.Stat(filepath.Join(".", "unit-test", datadirPrivateKey)); err == nil { t.Fatalf("ephemeral node key persisted to disk") } } diff --git a/node/doc.go b/node/doc.go new file mode 100644 index 000000000..f009e6f85 --- /dev/null +++ b/node/doc.go @@ -0,0 +1,90 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* +Package node sets up multi-protocol Ethereum nodes. + +In the model exposed by this package, a node is a collection of services which use shared +resources to provide RPC APIs. Services can also offer devp2p protocols, which are wired +up to the devp2p network when the node instance is started. + + +Resources Managed By Node + +All file-system resources used by a node instance are located in a directory called the +data directory. The location of each resource can be overridden through additional node +configuration. The data directory is optional. If it is not set and the location of a +resource is otherwise unspecified, package node will create the resource in memory. + +To access to the devp2p network, Node configures and starts p2p.Server. Each host on the +devp2p network has a unique identifier, the node key. The Node instance persists this key +across restarts. Node also loads static and trusted node lists and ensures that knowledge +about other hosts is persisted. + +JSON-RPC servers which run HTTP, WebSocket or IPC can be started on a Node. RPC modules +offered by registered services will be offered on those endpoints. Users can restrict any +endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3" +modules. + +Service implementations can open LevelDB databases through the service context. Package +node chooses the file system location of each database. If the node is configured to run +without a data directory, databases are opened in memory instead. + +Node also creates the shared store of encrypted Ethereum account keys. Services can access +the account manager through the service context. + + +Sharing Data Directory Among Instances + +Multiple node instances can share a single data directory if they have distinct instance +names (set through the Name config option). Sharing behaviour depends on the type of +resource. + +devp2p-related resources (node key, static/trusted node lists, known hosts database) are +stored in a directory with the same name as the instance. Thus, multiple node instances +using the same data directory will store this information in different subdirectories of +the data directory. + +LevelDB databases are also stored within the instance subdirectory. If multiple node +instances use the same data directory, openening the databases with identical names will +create one database for each instance. + +The account key store is shared among all node instances using the same data directory +unless its location is changed through the KeyStoreDir configuration option. + + +Data Directory Sharing Example + +In this exanple, two node instances named A and B are started with the same data +directory. Mode instance A opens the database "db", node instance B opens the databases +"db" and "db-2". The following files will be created in the data directory: + + data-directory/ + A/ + nodekey -- devp2p node key of instance A + nodes/ -- devp2p discovery knowledge database of instance A + db/ -- LevelDB content for "db" + A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A + B/ + nodekey -- devp2p node key of node B + nodes/ -- devp2p discovery knowledge database of instance B + static-nodes.json -- devp2p static node list of instance B + db/ -- LevelDB content for "db" + db-2/ -- LevelDB content for "db-2" + B.ipc -- JSON-RPC UNIX domain socket endpoint of instance A + keystore/ -- account key store, used by both instances +*/ +package node diff --git a/node/node.go b/node/node.go index f3be2f763..41c9eb27f 100644 --- a/node/node.go +++ b/node/node.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Package node represents the Ethereum protocol stack container. package node import ( @@ -23,16 +22,19 @@ import ( "os" "path/filepath" "reflect" + "strings" "sync" "syscall" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" + "github.com/syndtr/goleveldb/leveldb/storage" ) var ( @@ -44,14 +46,14 @@ var ( datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} ) -// Node represents a P2P node into which arbitrary (uniquely typed) services might -// be registered. +// Node is a container on which services can be registered. type Node struct { - datadir string // Path to the currently used data directory eventmux *event.TypeMux // Event multiplexer used between the services of a stack + config *Config + accman *accounts.Manager - accman *accounts.Manager - ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop + ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop + instanceDirLock storage.Storage // prevents concurrent use of instance directory serverConfig p2p.Config server *p2p.Server // Currently running P2P networking layer @@ -66,21 +68,14 @@ type Node struct { ipcListener net.Listener // IPC RPC listener socket to serve API requests ipcHandler *rpc.Server // IPC RPC request handler to process the API requests - httpHost string // HTTP hostname - httpPort int // HTTP post httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled) httpWhitelist []string // HTTP RPC modules to allow through this endpoint - httpCors string // HTTP RPC Cross-Origin Resource Sharing header httpListener net.Listener // HTTP RPC listener socket to server API requests httpHandler *rpc.Server // HTTP RPC request handler to process the API requests - wsHost string // Websocket host - wsPort int // Websocket post - wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled) - wsWhitelist []string // Websocket RPC modules to allow through this endpoint - wsOrigins string // Websocket RPC allowed origin domains - wsListener net.Listener // Websocket RPC listener socket to server API requests - wsHandler *rpc.Server // Websocket RPC request handler to process the API requests + wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled) + wsListener net.Listener // Websocket RPC listener socket to server API requests + wsHandler *rpc.Server // Websocket RPC request handler to process the API requests stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -88,54 +83,45 @@ type Node struct { // New creates a new P2P node, ready for protocol registration. func New(conf *Config) (*Node, error) { - // Ensure the data directory exists, failing if it cannot be created + // Copy config and resolve the datadir so future changes to the current + // working directory don't affect the node. + confCopy := *conf + conf = &confCopy if conf.DataDir != "" { - if err := os.MkdirAll(conf.DataDir, 0700); err != nil { + absdatadir, err := filepath.Abs(conf.DataDir) + if err != nil { return nil, err } + conf.DataDir = absdatadir } + // Ensure that the instance name doesn't cause weird conflicts with + // other files in the data directory. + if strings.ContainsAny(conf.Name, `/\`) { + return nil, errors.New(`Config.Name must not contain '/' or '\'`) + } + if conf.Name == datadirDefaultKeyStore { + return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) + } + if strings.HasSuffix(conf.Name, ".ipc") { + return nil, errors.New(`Config.Name cannot end in ".ipc"`) + } + // Ensure that the AccountManager method works before the node has started. + // We rely on this in cmd/geth. am, ephemeralKeystore, err := makeAccountManager(conf) if err != nil { return nil, err } - - // Assemble the networking layer and the node itself - nodeDbPath := "" - if conf.DataDir != "" { - nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase) - } + // Note: any interaction with Config that would create/touch files + // in the data directory or instance directory is delayed until Start. return &Node{ - datadir: conf.DataDir, accman: am, ephemeralKeystore: ephemeralKeystore, - serverConfig: p2p.Config{ - PrivateKey: conf.NodeKey(), - Name: conf.Name, - Discovery: !conf.NoDiscovery, - BootstrapNodes: conf.BootstrapNodes, - StaticNodes: conf.StaticNodes(), - TrustedNodes: conf.TrusterNodes(), - NodeDatabase: nodeDbPath, - ListenAddr: conf.ListenAddr, - NAT: conf.NAT, - Dialer: conf.Dialer, - NoDial: conf.NoDial, - MaxPeers: conf.MaxPeers, - MaxPendingPeers: conf.MaxPendingPeers, - }, - serviceFuncs: []ServiceConstructor{}, - ipcEndpoint: conf.IPCEndpoint(), - httpHost: conf.HTTPHost, - httpPort: conf.HTTPPort, - httpEndpoint: conf.HTTPEndpoint(), - httpWhitelist: conf.HTTPModules, - httpCors: conf.HTTPCors, - wsHost: conf.WSHost, - wsPort: conf.WSPort, - wsEndpoint: conf.WSEndpoint(), - wsWhitelist: conf.WSModules, - wsOrigins: conf.WSOrigins, - eventmux: new(event.TypeMux), + config: conf, + serviceFuncs: []ServiceConstructor{}, + ipcEndpoint: conf.IPCEndpoint(), + httpEndpoint: conf.HTTPEndpoint(), + wsEndpoint: conf.WSEndpoint(), + eventmux: new(event.TypeMux), }, nil } @@ -161,13 +147,36 @@ func (n *Node) Start() error { if n.server != nil { return ErrNodeRunning } - // Otherwise copy and specialize the P2P configuration + if err := n.openDataDir(); err != nil { + return err + } + + // Initialize the p2p server. This creates the node key and + // discovery databases. + n.serverConfig = p2p.Config{ + PrivateKey: n.config.NodeKey(), + Name: n.config.NodeName(), + Discovery: !n.config.NoDiscovery, + BootstrapNodes: n.config.BootstrapNodes, + StaticNodes: n.config.StaticNodes(), + TrustedNodes: n.config.TrusterNodes(), + NodeDatabase: n.config.NodeDB(), + ListenAddr: n.config.ListenAddr, + NAT: n.config.NAT, + Dialer: n.config.Dialer, + NoDial: n.config.NoDial, + MaxPeers: n.config.MaxPeers, + MaxPendingPeers: n.config.MaxPendingPeers, + } running := &p2p.Server{Config: n.serverConfig} + glog.V(logger.Info).Infoln("instance:", n.serverConfig.Name) + + // Otherwise copy and specialize the P2P configuration services := make(map[reflect.Type]Service) for _, constructor := range n.serviceFuncs { // Create a new context for the particular service ctx := &ServiceContext{ - datadir: n.datadir, + config: n.config, services: make(map[reflect.Type]Service), EventMux: n.eventmux, AccountManager: n.accman, @@ -227,6 +236,26 @@ func (n *Node) Start() error { return nil } +func (n *Node) openDataDir() error { + if n.config.DataDir == "" { + return nil // ephemeral + } + + instdir := filepath.Join(n.config.DataDir, n.config.name()) + if err := os.MkdirAll(instdir, 0700); err != nil { + return err + } + // Try to open the instance directory as LevelDB storage. This creates a lock file + // which prevents concurrent use by another instance as well as accidental use of the + // instance directory as a database. + storage, err := storage.OpenFile(instdir, true) + if err != nil { + return err + } + n.instanceDirLock = storage + return nil +} + // startRPC is a helper method to start all the various RPC endpoint during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. @@ -244,12 +273,12 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { n.stopInProc() return err } - if err := n.startHTTP(n.httpEndpoint, apis, n.httpWhitelist, n.httpCors); err != nil { + if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors); err != nil { n.stopIPC() n.stopInProc() return err } - if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsOrigins); err != nil { + if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins); err != nil { n.stopHTTP() n.stopIPC() n.stopInProc() @@ -381,7 +410,6 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors n.httpEndpoint = endpoint n.httpListener = listener n.httpHandler = handler - n.httpCors = cors return nil } @@ -436,7 +464,6 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig n.wsEndpoint = endpoint n.wsListener = listener n.wsHandler = handler - n.wsOrigins = wsOrigins return nil } @@ -465,12 +492,12 @@ func (n *Node) Stop() error { if n.server == nil { return ErrNodeStopped } - // Otherwise terminate the API, all services and the P2P server too + + // Terminate the API, services and the p2p server. n.stopWS() n.stopHTTP() n.stopIPC() n.rpcAPIs = nil - failure := &StopError{ Services: make(map[reflect.Type]error), } @@ -480,9 +507,16 @@ func (n *Node) Stop() error { } } n.server.Stop() - n.services = nil n.server = nil + + // Release instance directory lock. + if n.instanceDirLock != nil { + n.instanceDirLock.Close() + n.instanceDirLock = nil + } + + // unblock n.Wait close(n.stop) // Remove the keystore if it was created ephemerally. @@ -566,7 +600,7 @@ func (n *Node) Service(service interface{}) error { // DataDir retrieves the current datadir used by the protocol stack. func (n *Node) DataDir() string { - return n.datadir + return n.config.DataDir } // AccountManager retrieves the account manager used by the protocol stack. @@ -595,6 +629,21 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } +// OpenDatabase opens an existing database with the given name (or creates one if no +// previous can be found) from within the node's instance directory. If the node is +// ephemeral, a memory database is returned. +func (n *Node) OpenDatabase(name string, cache, handles int) (ethdb.Database, error) { + if n.config.DataDir == "" { + return ethdb.NewMemDatabase() + } + return ethdb.NewLDBDatabase(n.config.resolvePath(name), cache, handles) +} + +// ResolvePath returns the absolute path of a resource in the instance directory. +func (n *Node) ResolvePath(x string) string { + return n.config.resolvePath(x) +} + // apis returns the collection of RPC descriptors this node offers. func (n *Node) apis() []rpc.API { return []rpc.API{ diff --git a/node/service.go b/node/service.go index 51531466b..1cd1fe808 100644 --- a/node/service.go +++ b/node/service.go @@ -17,7 +17,6 @@ package node import ( - "path/filepath" "reflect" "github.com/ethereum/go-ethereum/accounts" @@ -31,7 +30,7 @@ import ( // the protocol stack, that is passed to all constructors to be optionally used; // as well as utility methods to operate on the service environment. type ServiceContext struct { - datadir string // Data directory for protocol persistence + config *Config services map[reflect.Type]Service // Index of the already constructed services EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. @@ -41,10 +40,10 @@ type ServiceContext struct { // if no previous can be found) from within the node's data directory. If the // node is an ephemeral one, a memory database is returned. func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int) (ethdb.Database, error) { - if ctx.datadir == "" { + if ctx.config.DataDir == "" { return ethdb.NewMemDatabase() } - return ethdb.NewLDBDatabase(filepath.Join(ctx.datadir, name), cache, handles) + return ethdb.NewLDBDatabase(ctx.config.resolvePath(name), cache, handles) } // Service retrieves a currently running service registered of a specific type. @@ -64,11 +63,13 @@ type ServiceConstructor func(ctx *ServiceContext) (Service, error) // Service is an individual protocol that can be registered into a node. // // Notes: -// - Service life-cycle management is delegated to the node. The service is -// allowed to initialize itself upon creation, but no goroutines should be -// spun up outside of the Start method. -// - Restart logic is not required as the node will create a fresh instance -// every time a service is started. +// +// • Service life-cycle management is delegated to the node. The service is allowed to +// initialize itself upon creation, but no goroutines should be spun up outside of the +// Start method. +// +// • Restart logic is not required as the node will create a fresh instance +// every time a service is started. type Service interface { // Protocols retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol diff --git a/node/service_test.go b/node/service_test.go index 7bd94a52e..a7ae439e0 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -38,18 +38,18 @@ func TestContextDatabases(t *testing.T) { t.Fatalf("non-created database already exists") } // Request the opening/creation of a database and ensure it persists to disk - ctx := &ServiceContext{datadir: dir} + ctx := &ServiceContext{config: &Config{Name: "unit-test", DataDir: dir}} db, err := ctx.OpenDatabase("persistent", 0, 0) if err != nil { t.Fatalf("failed to open persistent database: %v", err) } db.Close() - if _, err := os.Stat(filepath.Join(dir, "persistent")); err != nil { + if _, err := os.Stat(filepath.Join(dir, "unit-test", "persistent")); err != nil { t.Fatalf("persistent database doesn't exists: %v", err) } // Request th opening/creation of an ephemeral database and ensure it's not persisted - ctx = &ServiceContext{datadir: ""} + ctx = &ServiceContext{config: &Config{DataDir: ""}} db, err = ctx.OpenDatabase("ephemeral", 0, 0) if err != nil { t.Fatalf("failed to open ephemeral database: %v", err) From b42a5b118f1aa7ac1235547c8594146978941401 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 16 Sep 2016 11:53:50 +0200 Subject: [PATCH 4/4] common, node: move datadir defaults into package node --- cmd/gethrpctest/main.go | 9 ++++----- cmd/utils/customflags.go | 14 ++++++++++++-- cmd/utils/flags.go | 16 ++++++++-------- common/path.go | 27 --------------------------- node/api.go | 4 ++-- node/config.go | 6 +++--- {common => node}/defaults.go | 16 ++++++++++++++-- 7 files changed, 43 insertions(+), 49 deletions(-) rename {common => node}/defaults.go (89%) diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 2c35ec943..d0d6e1618 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -23,7 +23,6 @@ import ( "os" "os/signal" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -89,11 +88,11 @@ func MakeSystemNode(privkey string, test *tests.BlockTest) (*node.Node, error) { stack, err := node.New(&node.Config{ UseLightweightKDF: true, IPCPath: node.DefaultIPCEndpoint(""), - HTTPHost: common.DefaultHTTPHost, - HTTPPort: common.DefaultHTTPPort, + HTTPHost: node.DefaultHTTPHost, + HTTPPort: node.DefaultHTTPPort, HTTPModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"}, - WSHost: common.DefaultWSHost, - WSPort: common.DefaultWSPort, + WSHost: node.DefaultWSHost, + WSPort: node.DefaultWSPort, WSModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"}, NoDiscovery: true, }) diff --git a/cmd/utils/customflags.go b/cmd/utils/customflags.go index 5cbccfe98..11c92d451 100644 --- a/cmd/utils/customflags.go +++ b/cmd/utils/customflags.go @@ -137,9 +137,19 @@ func (self *DirectoryFlag) Set(value string) { // Note, it has limitations, e.g. ~someuser/tmp will not be expanded func expandPath(p string) string { if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { - if user, err := user.Current(); err == nil { - p = user.HomeDir + p[1:] + if home := homeDir(); home != "" { + p = home + p[1:] } } return path.Clean(os.ExpandEnv(p)) } + +func homeDir() string { + if home := os.Getenv("HOME"); home != "" { + return home + } + if usr, err := user.Current(); err == nil { + return usr.HomeDir + } + return "" +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 47a8ebd41..0be499c5b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -105,7 +105,7 @@ var ( DataDirFlag = DirectoryFlag{ Name: "datadir", Usage: "Data directory for the databases and keystore", - Value: DirectoryString{common.DefaultDataDir()}, + Value: DirectoryString{node.DefaultDataDir()}, } KeyStoreDirFlag = DirectoryFlag{ Name: "keystore", @@ -139,7 +139,7 @@ var ( DocRootFlag = DirectoryFlag{ Name: "docroot", Usage: "Document Root for HTTPClient file scheme", - Value: DirectoryString{common.HomeDir()}, + Value: DirectoryString{homeDir()}, } CacheFlag = cli.IntFlag{ Name: "cache", @@ -245,12 +245,12 @@ var ( RPCListenAddrFlag = cli.StringFlag{ Name: "rpcaddr", Usage: "HTTP-RPC server listening interface", - Value: common.DefaultHTTPHost, + Value: node.DefaultHTTPHost, } RPCPortFlag = cli.IntFlag{ Name: "rpcport", Usage: "HTTP-RPC server listening port", - Value: common.DefaultHTTPPort, + Value: node.DefaultHTTPPort, } RPCCORSDomainFlag = cli.StringFlag{ Name: "rpccorsdomain", @@ -268,13 +268,13 @@ var ( } IPCApiFlag = cli.StringFlag{ Name: "ipcapi", - Usage: "API's offered over the IPC-RPC interface", + Usage: "APIs offered over the IPC-RPC interface", Value: rpc.DefaultIPCApis, } IPCPathFlag = DirectoryFlag{ Name: "ipcpath", Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)", - Value: DirectoryString{common.DefaultIPCSocket}, + Value: DirectoryString{"geth.ipc"}, } WSEnabledFlag = cli.BoolFlag{ Name: "ws", @@ -283,12 +283,12 @@ var ( WSListenAddrFlag = cli.StringFlag{ Name: "wsaddr", Usage: "WS-RPC server listening interface", - Value: common.DefaultWSHost, + Value: node.DefaultWSHost, } WSPortFlag = cli.IntFlag{ Name: "wsport", Usage: "WS-RPC server listening port", - Value: common.DefaultWSPort, + Value: node.DefaultWSPort, } WSApiFlag = cli.StringFlag{ Name: "wsapi", diff --git a/common/path.go b/common/path.go index cbcd13c4f..bd8da86e7 100644 --- a/common/path.go +++ b/common/path.go @@ -19,10 +19,8 @@ package common import ( "fmt" "os" - "os/user" "path/filepath" "runtime" - "strings" ) // MakeName creates a node name that follows the ethereum convention @@ -32,21 +30,6 @@ func MakeName(name, version string) string { return fmt.Sprintf("%s/v%s/%s/%s", name, version, runtime.GOOS, runtime.Version()) } -func ExpandHomePath(p string) (path string) { - path = p - sep := string(os.PathSeparator) - - // Check in case of paths like "/something/~/something/" - if len(p) > 1 && p[:1+len(sep)] == "~"+sep { - usr, _ := user.Current() - dir := usr.HomeDir - - path = strings.Replace(p, "~", dir, 1) - } - - return -} - func FileExist(filePath string) bool { _, err := os.Stat(filePath) if err != nil && os.IsNotExist(err) { @@ -62,13 +45,3 @@ func AbsolutePath(Datadir string, filename string) string { } return filepath.Join(Datadir, filename) } - -func HomeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - if usr, err := user.Current(); err == nil { - return usr.HomeDir - } - return "" -} diff --git a/node/api.go b/node/api.go index 2942705d9..631e92c8e 100644 --- a/node/api.go +++ b/node/api.go @@ -84,7 +84,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *st } if host == nil { - h := common.DefaultHTTPHost + h := DefaultHTTPHost if api.node.config.HTTPHost != "" { h = api.node.config.HTTPHost } @@ -133,7 +133,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOr } if host == nil { - h := common.DefaultWSHost + h := DefaultWSHost if api.node.config.WSHost != "" { h = api.node.config.WSHost } diff --git a/node/config.go b/node/config.go index 4a18432c7..15884a12e 100644 --- a/node/config.go +++ b/node/config.go @@ -201,7 +201,7 @@ func DefaultIPCEndpoint(clientIdentifier string) string { panic("empty executable name") } } - config := &Config{DataDir: common.DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} + config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} return config.IPCEndpoint() } @@ -216,7 +216,7 @@ func (c *Config) HTTPEndpoint() string { // DefaultHTTPEndpoint returns the HTTP endpoint used by default. func DefaultHTTPEndpoint() string { - config := &Config{HTTPHost: common.DefaultHTTPHost, HTTPPort: common.DefaultHTTPPort} + config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} return config.HTTPEndpoint() } @@ -231,7 +231,7 @@ func (c *Config) WSEndpoint() string { // DefaultWSEndpoint returns the websocket endpoint used by default. func DefaultWSEndpoint() string { - config := &Config{WSHost: common.DefaultWSHost, WSPort: common.DefaultWSPort} + config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort} return config.WSEndpoint() } diff --git a/common/defaults.go b/node/defaults.go similarity index 89% rename from common/defaults.go rename to node/defaults.go index 8a136fa80..bfe257c8e 100644 --- a/common/defaults.go +++ b/node/defaults.go @@ -14,9 +14,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package common +package node import ( + "os" + "os/user" "path/filepath" "runtime" ) @@ -33,7 +35,7 @@ const ( // persistence requirements. func DefaultDataDir() string { // Try to place the data folder in the user's home dir - home := HomeDir() + home := homeDir() if home != "" { if runtime.GOOS == "darwin" { return filepath.Join(home, "Library", "Ethereum") @@ -46,3 +48,13 @@ func DefaultDataDir() string { // As we cannot guess a stable location, return empty and handle later return "" } + +func homeDir() string { + if home := os.Getenv("HOME"); home != "" { + return home + } + if usr, err := user.Current(); err == nil { + return usr.HomeDir + } + return "" +}