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