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) {
|
if ctx.GlobalBool(utils.TestNetFlag.Name) {
|
||||||
state.StartingNonce = 1048576 // (2**20)
|
state.StartingNonce = 1048576 // (2**20)
|
||||||
}
|
}
|
||||||
chain, chainDb := utils.MakeChain(ctx)
|
stack := makeFullNode(ctx)
|
||||||
|
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := utils.ImportChain(chain, ctx.Args().First())
|
err := utils.ImportChain(chain, ctx.Args().First())
|
||||||
chainDb.Close()
|
chainDb.Close()
|
||||||
@ -94,7 +95,8 @@ func exportChain(ctx *cli.Context) error {
|
|||||||
if len(ctx.Args()) < 1 {
|
if len(ctx.Args()) < 1 {
|
||||||
utils.Fatalf("This command requires an argument.")
|
utils.Fatalf("This command requires an argument.")
|
||||||
}
|
}
|
||||||
chain, _ := utils.MakeChain(ctx)
|
stack := makeFullNode(ctx)
|
||||||
|
chain, _ := utils.MakeChain(ctx, stack)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -122,20 +124,25 @@ func exportChain(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func removeDB(ctx *cli.Context) error {
|
func removeDB(ctx *cli.Context) error {
|
||||||
confirm, err := console.Stdin.PromptConfirm("Remove local database?")
|
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
||||||
if err != nil {
|
dbdir := stack.ResolvePath("chaindata")
|
||||||
utils.Fatalf("%v", err)
|
if !common.FileExist(dbdir) {
|
||||||
|
fmt.Println(dbdir, "does not exist")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if confirm {
|
fmt.Println(dbdir)
|
||||||
fmt.Println("Removing chaindata...")
|
confirm, err := console.Stdin.PromptConfirm("Remove this database?")
|
||||||
start := time.Now()
|
switch {
|
||||||
|
case err != nil:
|
||||||
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "chaindata"))
|
utils.Fatalf("%v", err)
|
||||||
|
case !confirm:
|
||||||
fmt.Printf("Removed in %v\n", time.Since(start))
|
|
||||||
} else {
|
|
||||||
fmt.Println("Operation aborted")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@ -143,7 +150,8 @@ func removeDB(ctx *cli.Context) error {
|
|||||||
func upgradeDB(ctx *cli.Context) error {
|
func upgradeDB(ctx *cli.Context) error {
|
||||||
glog.Infoln("Upgrading blockchain database")
|
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)
|
bcVersion := core.GetBlockChainVersion(chainDb)
|
||||||
if bcVersion == 0 {
|
if bcVersion == 0 {
|
||||||
bcVersion = core.BlockChainVersion
|
bcVersion = core.BlockChainVersion
|
||||||
@ -156,10 +164,12 @@ func upgradeDB(ctx *cli.Context) error {
|
|||||||
utils.Fatalf("Unable to export chain for reimport %s", err)
|
utils.Fatalf("Unable to export chain for reimport %s", err)
|
||||||
}
|
}
|
||||||
chainDb.Close()
|
chainDb.Close()
|
||||||
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "chaindata"))
|
if dir := dbDirectory(chainDb); dir != "" {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
}
|
||||||
|
|
||||||
// Import the chain file.
|
// Import the chain file.
|
||||||
chain, chainDb = utils.MakeChain(ctx)
|
chain, chainDb = utils.MakeChain(ctx, stack)
|
||||||
core.WriteBlockChainVersion(chainDb, core.BlockChainVersion)
|
core.WriteBlockChainVersion(chainDb, core.BlockChainVersion)
|
||||||
err := utils.ImportChain(chain, exportFile)
|
err := utils.ImportChain(chain, exportFile)
|
||||||
chainDb.Close()
|
chainDb.Close()
|
||||||
@ -172,8 +182,17 @@ func upgradeDB(ctx *cli.Context) error {
|
|||||||
return nil
|
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 {
|
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() {
|
for _, arg := range ctx.Args() {
|
||||||
var block *types.Block
|
var block *types.Block
|
||||||
if hashish(arg) {
|
if hashish(arg) {
|
||||||
|
@ -107,7 +107,7 @@ func remoteConsole(ctx *cli.Context) error {
|
|||||||
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
||||||
}
|
}
|
||||||
config := console.Config{
|
config := console.Config{
|
||||||
DataDir: utils.MustMakeDataDir(ctx),
|
DataDir: utils.MakeDataDir(ctx),
|
||||||
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
||||||
Client: client,
|
Client: client,
|
||||||
Preload: utils.MakeConsolePreloads(ctx),
|
Preload: utils.MakeConsolePreloads(ctx),
|
||||||
@ -135,7 +135,7 @@ func remoteConsole(ctx *cli.Context) error {
|
|||||||
// for "geth attach" and "geth monitor" with no argument.
|
// for "geth attach" and "geth monitor" with no argument.
|
||||||
func dialRPC(endpoint string) (*rpc.Client, error) {
|
func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
endpoint = node.DefaultIPCEndpoint()
|
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
|
||||||
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
|
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
|
||||||
// Backwards compatibility with geth < 1.5 which required
|
// Backwards compatibility with geth < 1.5 which required
|
||||||
// these prefixes.
|
// these prefixes.
|
||||||
|
@ -195,9 +195,9 @@ func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes
|
|||||||
geth.cmd.Wait()
|
geth.cmd.Wait()
|
||||||
}
|
}
|
||||||
// Retrieve the DAO config flag from the database
|
// Retrieve the DAO config flag from the database
|
||||||
path := filepath.Join(datadir, "chaindata")
|
path := filepath.Join(datadir, "geth", "chaindata")
|
||||||
if testnet && genesis == "" {
|
if testnet && genesis == "" {
|
||||||
path = filepath.Join(datadir, "testnet", "chaindata")
|
path = filepath.Join(datadir, "testnet", "geth", "chaindata")
|
||||||
}
|
}
|
||||||
db, err := ethdb.NewLDBDatabase(path, 0, 0)
|
db, err := ethdb.NewLDBDatabase(path, 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,7 +36,6 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
@ -46,7 +45,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
clientIdentifier = "Geth" // Client identifier to advertise over the network
|
clientIdentifier = "geth" // Client identifier to advertise over the network
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -245,17 +244,15 @@ func initGenesis(ctx *cli.Context) error {
|
|||||||
state.StartingNonce = 1048576 // (2**20)
|
state.StartingNonce = 1048576 // (2**20)
|
||||||
}
|
}
|
||||||
|
|
||||||
chainDb, err := ethdb.NewLDBDatabase(filepath.Join(utils.MustMakeDataDir(ctx), "chaindata"), 0, 0)
|
stack := makeFullNode(ctx)
|
||||||
if err != nil {
|
chaindb := utils.MakeChainDatabase(ctx, stack)
|
||||||
utils.Fatalf("could not open database: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
genesisFile, err := os.Open(genesisPath)
|
genesisFile, err := os.Open(genesisPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("failed to read genesis file: %v", err)
|
utils.Fatalf("failed to read genesis file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := core.WriteGenesisBlock(chainDb, genesisFile)
|
block, err := core.WriteGenesisBlock(chaindb, genesisFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("failed to write genesis block: %v", err)
|
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
|
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
||||||
// miner.
|
// miner.
|
||||||
func startNode(ctx *cli.Context, stack *node.Node) {
|
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
|
// Start up the node itself
|
||||||
utils.StartNode(stack)
|
utils.StartNode(stack)
|
||||||
|
|
||||||
@ -379,7 +373,7 @@ func gpubench(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func version(c *cli.Context) error {
|
func version(c *cli.Context) error {
|
||||||
fmt.Println(clientIdentifier)
|
fmt.Println(strings.Title(clientIdentifier))
|
||||||
fmt.Println("Version:", utils.Version)
|
fmt.Println("Version:", utils.Version)
|
||||||
if gitCommit != "" {
|
if gitCommit != "" {
|
||||||
fmt.Println("Git Commit:", gitCommit)
|
fmt.Println("Git Commit:", gitCommit)
|
||||||
|
@ -35,7 +35,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
monitorCommandAttachFlag = cli.StringFlag{
|
monitorCommandAttachFlag = cli.StringFlag{
|
||||||
Name: "attach",
|
Name: "attach",
|
||||||
Value: node.DefaultIPCEndpoint(),
|
Value: node.DefaultIPCEndpoint(clientIdentifier),
|
||||||
Usage: "API endpoint to attach to",
|
Usage: "API endpoint to attach to",
|
||||||
}
|
}
|
||||||
monitorCommandRowsFlag = cli.IntFlag{
|
monitorCommandRowsFlag = cli.IntFlag{
|
||||||
|
@ -88,7 +88,7 @@ func MakeSystemNode(privkey string, test *tests.BlockTest) (*node.Node, error) {
|
|||||||
// Create a networkless protocol stack
|
// Create a networkless protocol stack
|
||||||
stack, err := node.New(&node.Config{
|
stack, err := node.New(&node.Config{
|
||||||
UseLightweightKDF: true,
|
UseLightweightKDF: true,
|
||||||
IPCPath: node.DefaultIPCEndpoint(),
|
IPCPath: node.DefaultIPCEndpoint(""),
|
||||||
HTTPHost: common.DefaultHTTPHost,
|
HTTPHost: common.DefaultHTTPHost,
|
||||||
HTTPPort: common.DefaultHTTPPort,
|
HTTPPort: common.DefaultHTTPPort,
|
||||||
HTTPModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"},
|
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,
|
// 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.
|
// 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 != "" {
|
if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
|
||||||
|
// TODO: choose a different location outside of the regular datadir.
|
||||||
if ctx.GlobalBool(TestNetFlag.Name) {
|
if ctx.GlobalBool(TestNetFlag.Name) {
|
||||||
return filepath.Join(path, "/testnet")
|
return filepath.Join(path, "testnet")
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -447,16 +448,16 @@ func MakeNodeKey(ctx *cli.Context) *ecdsa.PrivateKey {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeNodeName creates a node name from a base set and the command line flags.
|
// makeNodeUserIdent creates the user identifier from CLI flags.
|
||||||
func MakeNodeName(client, version string, ctx *cli.Context) string {
|
func makeNodeUserIdent(ctx *cli.Context) string {
|
||||||
name := common.MakeName(client, version)
|
var comps []string
|
||||||
if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 {
|
if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 {
|
||||||
name += "/" + identity
|
comps = append(comps, identity)
|
||||||
}
|
}
|
||||||
if ctx.GlobalBool(VMEnableJitFlag.Name) {
|
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
|
// 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{
|
config := &node.Config{
|
||||||
DataDir: MustMakeDataDir(ctx),
|
DataDir: MakeDataDir(ctx),
|
||||||
KeyStoreDir: ctx.GlobalString(KeyStoreDirFlag.Name),
|
KeyStoreDir: ctx.GlobalString(KeyStoreDirFlag.Name),
|
||||||
UseLightweightKDF: ctx.GlobalBool(LightKDFFlag.Name),
|
UseLightweightKDF: ctx.GlobalBool(LightKDFFlag.Name),
|
||||||
PrivateKey: MakeNodeKey(ctx),
|
PrivateKey: MakeNodeKey(ctx),
|
||||||
Name: MakeNodeName(name, vsn, ctx),
|
Name: name,
|
||||||
|
Version: vsn,
|
||||||
|
UserIdent: makeNodeUserIdent(ctx),
|
||||||
NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name),
|
NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name),
|
||||||
BootstrapNodes: MakeBootstrapNodes(ctx),
|
BootstrapNodes: MakeBootstrapNodes(ctx),
|
||||||
ListenAddr: MakeListenAddress(ctx),
|
ListenAddr: MakeListenAddress(ctx),
|
||||||
@ -674,7 +677,7 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
|
|||||||
|
|
||||||
ethConf := ð.Config{
|
ethConf := ð.Config{
|
||||||
Etherbase: MakeEtherbase(stack.AccountManager(), ctx),
|
Etherbase: MakeEtherbase(stack.AccountManager(), ctx),
|
||||||
ChainConfig: MustMakeChainConfig(ctx),
|
ChainConfig: MakeChainConfig(ctx, stack),
|
||||||
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
|
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
|
||||||
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
|
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
|
||||||
DatabaseHandles: MakeDatabaseHandles(),
|
DatabaseHandles: MakeDatabaseHandles(),
|
||||||
@ -748,16 +751,16 @@ func SetupNetwork(ctx *cli.Context) {
|
|||||||
params.TargetGasLimit = common.String2Big(ctx.GlobalString(TargetGasLimitFlag.Name))
|
params.TargetGasLimit = common.String2Big(ctx.GlobalString(TargetGasLimitFlag.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustMakeChainConfig reads the chain configuration from the database in ctx.Datadir.
|
// MakeChainConfig reads the chain configuration from the database in ctx.Datadir.
|
||||||
func MustMakeChainConfig(ctx *cli.Context) *core.ChainConfig {
|
func MakeChainConfig(ctx *cli.Context, stack *node.Node) *core.ChainConfig {
|
||||||
db := MakeChainDatabase(ctx)
|
db := MakeChainDatabase(ctx, stack)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
return MustMakeChainConfigFromDb(ctx, db)
|
return MakeChainConfigFromDb(ctx, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustMakeChainConfigFromDb reads the chain configuration from the given database.
|
// MakeChainConfigFromDb reads the chain configuration from the given database.
|
||||||
func MustMakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig {
|
func MakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig {
|
||||||
// If the chain is already initialized, use any existing chain configs
|
// If the chain is already initialized, use any existing chain configs
|
||||||
config := new(core.ChainConfig)
|
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.
|
// 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 (
|
var (
|
||||||
datadir = MustMakeDataDir(ctx)
|
|
||||||
cache = ctx.GlobalInt(CacheFlag.Name)
|
cache = ctx.GlobalInt(CacheFlag.Name)
|
||||||
handles = MakeDatabaseHandles()
|
handles = MakeDatabaseHandles()
|
||||||
)
|
)
|
||||||
|
|
||||||
chainDb, err := ethdb.NewLDBDatabase(filepath.Join(datadir, "chaindata"), cache, handles)
|
chainDb, err := stack.OpenDatabase("chaindata", cache, handles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("Could not open database: %v", err)
|
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.
|
// 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
|
var err error
|
||||||
chainDb = MakeChainDatabase(ctx)
|
chainDb = MakeChainDatabase(ctx, stack)
|
||||||
|
|
||||||
if ctx.GlobalBool(OlympicFlag.Name) {
|
if ctx.GlobalBool(OlympicFlag.Name) {
|
||||||
_, err := core.WriteTestNetGenesisBlock(chainDb)
|
_, err := core.WriteTestNetGenesisBlock(chainDb)
|
||||||
@ -825,7 +827,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
|
|||||||
glog.Fatalln(err)
|
glog.Fatalln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chainConfig := MustMakeChainConfigFromDb(ctx, chainDb)
|
chainConfig := MakeChainConfigFromDb(ctx, chainDb)
|
||||||
|
|
||||||
pow := pow.PoW(core.FakePow{})
|
pow := pow.PoW(core.FakePow{})
|
||||||
if !ctx.GlobalBool(FakePoWFlag.Name) {
|
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 {
|
if host == nil {
|
||||||
h := common.DefaultHTTPHost
|
h := common.DefaultHTTPHost
|
||||||
if api.node.httpHost != "" {
|
if api.node.config.HTTPHost != "" {
|
||||||
h = api.node.httpHost
|
h = api.node.config.HTTPHost
|
||||||
}
|
}
|
||||||
host = &h
|
host = &h
|
||||||
}
|
}
|
||||||
if port == nil {
|
if port == nil {
|
||||||
port = rpc.NewHexNumber(api.node.httpPort)
|
port = rpc.NewHexNumber(api.node.config.HTTPPort)
|
||||||
}
|
}
|
||||||
if cors == nil {
|
if cors == nil {
|
||||||
cors = &api.node.httpCors
|
cors = &api.node.config.HTTPCors
|
||||||
}
|
}
|
||||||
|
|
||||||
modules := api.node.httpWhitelist
|
modules := api.node.httpWhitelist
|
||||||
@ -134,19 +134,19 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOr
|
|||||||
|
|
||||||
if host == nil {
|
if host == nil {
|
||||||
h := common.DefaultWSHost
|
h := common.DefaultWSHost
|
||||||
if api.node.wsHost != "" {
|
if api.node.config.WSHost != "" {
|
||||||
h = api.node.wsHost
|
h = api.node.config.WSHost
|
||||||
}
|
}
|
||||||
host = &h
|
host = &h
|
||||||
}
|
}
|
||||||
if port == nil {
|
if port == nil {
|
||||||
port = rpc.NewHexNumber(api.node.wsPort)
|
port = rpc.NewHexNumber(api.node.config.WSPort)
|
||||||
}
|
}
|
||||||
if allowedOrigins == nil {
|
if allowedOrigins == nil {
|
||||||
allowedOrigins = &api.node.wsOrigins
|
allowedOrigins = &api.node.config.WSOrigins
|
||||||
}
|
}
|
||||||
|
|
||||||
modules := api.node.wsWhitelist
|
modules := api.node.config.WSModules
|
||||||
if apis != nil {
|
if apis != nil {
|
||||||
modules = nil
|
modules = nil
|
||||||
for _, m := range strings.Split(*apis, ",") {
|
for _, m := range strings.Split(*apis, ",") {
|
||||||
|
129
node/config.go
129
node/config.go
@ -18,7 +18,6 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -48,6 +47,18 @@ var (
|
|||||||
// P2P network layer of a protocol stack. These values can be further extended by
|
// P2P network layer of a protocol stack. These values can be further extended by
|
||||||
// all registered services.
|
// all registered services.
|
||||||
type Config struct {
|
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
|
// 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
|
// requirements. The configured data directory will not be directly shared with
|
||||||
// registered services, instead those can use utility methods to create/access
|
// registered services, instead those can use utility methods to create/access
|
||||||
@ -80,10 +91,6 @@ type Config struct {
|
|||||||
// needed.
|
// needed.
|
||||||
PrivateKey *ecdsa.PrivateKey
|
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
|
// NoDiscovery specifies whether the peer discovery mechanism should be started
|
||||||
// or not. Disabling is usually useful for protocol debugging (manual topology).
|
// or not. Disabling is usually useful for protocol debugging (manual topology).
|
||||||
NoDiscovery bool
|
NoDiscovery bool
|
||||||
@ -178,9 +185,23 @@ func (c *Config) IPCEndpoint() string {
|
|||||||
return c.IPCPath
|
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.
|
// DefaultIPCEndpoint returns the IPC path used by default.
|
||||||
func DefaultIPCEndpoint() string {
|
func DefaultIPCEndpoint(clientIdentifier string) string {
|
||||||
config := &Config{DataDir: common.DefaultDataDir(), IPCPath: common.DefaultIPCSocket}
|
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()
|
return config.IPCEndpoint()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,15 +235,76 @@ func DefaultWSEndpoint() string {
|
|||||||
return config.WSEndpoint()
|
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
|
// 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
|
// 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.
|
// data folder. If no key can be found, a new one is generated.
|
||||||
func (c *Config) NodeKey() *ecdsa.PrivateKey {
|
func (c *Config) NodeKey() *ecdsa.PrivateKey {
|
||||||
// Use any specifically configured key
|
// Use any specifically configured key.
|
||||||
if c.PrivateKey != nil {
|
if c.PrivateKey != nil {
|
||||||
return c.PrivateKey
|
return c.PrivateKey
|
||||||
}
|
}
|
||||||
// Generate ephemeral key if no datadir is being used
|
// Generate ephemeral key if no datadir is being used.
|
||||||
if c.DataDir == "" {
|
if c.DataDir == "" {
|
||||||
key, err := crypto.GenerateKey()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -230,16 +312,22 @@ func (c *Config) NodeKey() *ecdsa.PrivateKey {
|
|||||||
}
|
}
|
||||||
return key
|
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 {
|
if key, err := crypto.LoadECDSA(keyfile); err == nil {
|
||||||
return key
|
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()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Failed to generate node key: %v", err)
|
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 {
|
if err := crypto.SaveECDSA(keyfile, key); err != nil {
|
||||||
glog.V(logger.Error).Infof("Failed to persist node key: %v", err)
|
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.
|
// StaticNodes returns a list of node enode URLs configured as static nodes.
|
||||||
func (c *Config) StaticNodes() []*discover.Node {
|
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.
|
// TrusterNodes returns a list of node enode URLs configured as trusted nodes.
|
||||||
func (c *Config) TrusterNodes() []*discover.Node {
|
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
|
// 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 {
|
if _, err := os.Stat(path); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Load the nodes from the config file
|
// Load the nodes from the config file.
|
||||||
blob, err := ioutil.ReadFile(path)
|
var nodelist []string
|
||||||
if err != nil {
|
if err := common.LoadJSON(path, &nodelist); err != nil {
|
||||||
glog.V(logger.Error).Infof("Failed to access nodes: %v", err)
|
glog.V(logger.Error).Infof("Can't load node file %s: %v", path, err)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nodelist := []string{}
|
|
||||||
if err := json.Unmarshal(blob, &nodelist); err != nil {
|
|
||||||
glog.V(logger.Error).Infof("Failed to load nodes: %v", err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Interpret the list as a discovery node array
|
// Interpret the list as a discovery node array
|
||||||
|
@ -96,57 +96,55 @@ func TestIPCPathResolution(t *testing.T) {
|
|||||||
// ephemeral.
|
// ephemeral.
|
||||||
func TestNodeKeyPersistency(t *testing.T) {
|
func TestNodeKeyPersistency(t *testing.T) {
|
||||||
// Create a temporary folder and make sure no key is present
|
// Create a temporary folder and make sure no key is present
|
||||||
dir, err := ioutil.TempDir("", "")
|
dir, err := ioutil.TempDir("", "node-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create temporary data directory: %v", err)
|
t.Fatalf("failed to create temporary data directory: %v", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil {
|
keyfile := filepath.Join(dir, "unit-test", datadirPrivateKey)
|
||||||
t.Fatalf("non-created node key already exists")
|
|
||||||
}
|
|
||||||
// Configure a node with a preset key and ensure it's not persisted
|
// Configure a node with a preset key and ensure it's not persisted
|
||||||
key, err := crypto.GenerateKey()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate one-shot node key: %v", err)
|
t.Fatalf("failed to generate one-shot node key: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil {
|
config := &Config{Name: "unit-test", DataDir: dir, PrivateKey: key}
|
||||||
t.Fatalf("failed to create empty stack: %v", err)
|
config.NodeKey()
|
||||||
}
|
if _, err := os.Stat(filepath.Join(keyfile)); err == nil {
|
||||||
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil {
|
|
||||||
t.Fatalf("one-shot node key persisted to data directory")
|
t.Fatalf("one-shot node key persisted to data directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure a node with no preset key and ensure it is persisted this time
|
// Configure a node with no preset key and ensure it is persisted this time
|
||||||
if _, err := New(&Config{DataDir: dir}); err != nil {
|
config = &Config{Name: "unit-test", DataDir: dir}
|
||||||
t.Fatalf("failed to create newly keyed stack: %v", err)
|
config.NodeKey()
|
||||||
}
|
if _, err := os.Stat(keyfile); err != nil {
|
||||||
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil {
|
|
||||||
t.Fatalf("node key not persisted to data directory: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to load freshly persisted node key: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to read freshly persisted node key: %v", err)
|
t.Fatalf("failed to read freshly persisted node key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure a new node and ensure the previously persisted key is loaded
|
// Configure a new node and ensure the previously persisted key is loaded
|
||||||
if _, err := New(&Config{DataDir: dir}); err != nil {
|
config = &Config{Name: "unit-test", DataDir: dir}
|
||||||
t.Fatalf("failed to create previously keyed stack: %v", err)
|
config.NodeKey()
|
||||||
}
|
blob2, err := ioutil.ReadFile(filepath.Join(keyfile))
|
||||||
blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read previously persisted node key: %v", err)
|
t.Fatalf("failed to read previously persisted node key: %v", err)
|
||||||
}
|
}
|
||||||
if bytes.Compare(blob1, blob2) != 0 {
|
if bytes.Compare(blob1, blob2) != 0 {
|
||||||
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1)
|
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure ephemeral node and ensure no key is dumped locally
|
// Configure ephemeral node and ensure no key is dumped locally
|
||||||
if _, err := New(&Config{DataDir: ""}); err != nil {
|
config = &Config{Name: "unit-test", DataDir: ""}
|
||||||
t.Fatalf("failed to create ephemeral stack: %v", err)
|
config.NodeKey()
|
||||||
}
|
if _, err := os.Stat(filepath.Join(".", "unit-test", datadirPrivateKey)); err == nil {
|
||||||
if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil {
|
|
||||||
t.Fatalf("ephemeral node key persisted to disk")
|
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
|
157
node/node.go
157
node/node.go
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU Lesser General Public License
|
// 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/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package node represents the Ethereum protocol stack container.
|
|
||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -23,16 +22,19 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -44,14 +46,14 @@ var (
|
|||||||
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
|
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node represents a P2P node into which arbitrary (uniquely typed) services might
|
// Node is a container on which services can be registered.
|
||||||
// be registered.
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
datadir string // Path to the currently used data directory
|
|
||||||
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
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
|
serverConfig p2p.Config
|
||||||
server *p2p.Server // Currently running P2P networking layer
|
server *p2p.Server // Currently running P2P networking layer
|
||||||
@ -66,19 +68,12 @@ type Node struct {
|
|||||||
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
ipcListener net.Listener // IPC RPC listener socket to serve API requests
|
||||||
ipcHandler *rpc.Server // IPC RPC request handler to process the 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)
|
httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
|
||||||
httpWhitelist []string // HTTP RPC modules to allow through this endpoint
|
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
|
httpListener net.Listener // HTTP RPC listener socket to server API requests
|
||||||
httpHandler *rpc.Server // HTTP RPC request handler to process the 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)
|
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
|
wsListener net.Listener // Websocket RPC listener socket to server API requests
|
||||||
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
wsHandler *rpc.Server // Websocket RPC request handler to process the API requests
|
||||||
|
|
||||||
@ -88,53 +83,44 @@ type Node struct {
|
|||||||
|
|
||||||
// New creates a new P2P node, ready for protocol registration.
|
// New creates a new P2P node, ready for protocol registration.
|
||||||
func New(conf *Config) (*Node, error) {
|
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 conf.DataDir != "" {
|
||||||
if err := os.MkdirAll(conf.DataDir, 0700); err != nil {
|
absdatadir, err := filepath.Abs(conf.DataDir)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
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)
|
am, ephemeralKeystore, err := makeAccountManager(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Note: any interaction with Config that would create/touch files
|
||||||
// Assemble the networking layer and the node itself
|
// in the data directory or instance directory is delayed until Start.
|
||||||
nodeDbPath := ""
|
|
||||||
if conf.DataDir != "" {
|
|
||||||
nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase)
|
|
||||||
}
|
|
||||||
return &Node{
|
return &Node{
|
||||||
datadir: conf.DataDir,
|
|
||||||
accman: am,
|
accman: am,
|
||||||
ephemeralKeystore: ephemeralKeystore,
|
ephemeralKeystore: ephemeralKeystore,
|
||||||
serverConfig: p2p.Config{
|
config: conf,
|
||||||
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{},
|
serviceFuncs: []ServiceConstructor{},
|
||||||
ipcEndpoint: conf.IPCEndpoint(),
|
ipcEndpoint: conf.IPCEndpoint(),
|
||||||
httpHost: conf.HTTPHost,
|
|
||||||
httpPort: conf.HTTPPort,
|
|
||||||
httpEndpoint: conf.HTTPEndpoint(),
|
httpEndpoint: conf.HTTPEndpoint(),
|
||||||
httpWhitelist: conf.HTTPModules,
|
|
||||||
httpCors: conf.HTTPCors,
|
|
||||||
wsHost: conf.WSHost,
|
|
||||||
wsPort: conf.WSPort,
|
|
||||||
wsEndpoint: conf.WSEndpoint(),
|
wsEndpoint: conf.WSEndpoint(),
|
||||||
wsWhitelist: conf.WSModules,
|
|
||||||
wsOrigins: conf.WSOrigins,
|
|
||||||
eventmux: new(event.TypeMux),
|
eventmux: new(event.TypeMux),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -161,13 +147,36 @@ func (n *Node) Start() error {
|
|||||||
if n.server != nil {
|
if n.server != nil {
|
||||||
return ErrNodeRunning
|
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}
|
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)
|
services := make(map[reflect.Type]Service)
|
||||||
for _, constructor := range n.serviceFuncs {
|
for _, constructor := range n.serviceFuncs {
|
||||||
// Create a new context for the particular service
|
// Create a new context for the particular service
|
||||||
ctx := &ServiceContext{
|
ctx := &ServiceContext{
|
||||||
datadir: n.datadir,
|
config: n.config,
|
||||||
services: make(map[reflect.Type]Service),
|
services: make(map[reflect.Type]Service),
|
||||||
EventMux: n.eventmux,
|
EventMux: n.eventmux,
|
||||||
AccountManager: n.accman,
|
AccountManager: n.accman,
|
||||||
@ -227,6 +236,26 @@ func (n *Node) Start() error {
|
|||||||
return nil
|
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
|
// 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
|
// startup. It's not meant to be called at any time afterwards as it makes certain
|
||||||
// assumptions about the state of the node.
|
// assumptions about the state of the node.
|
||||||
@ -244,12 +273,12 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error {
|
|||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
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.stopIPC()
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
return err
|
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.stopHTTP()
|
||||||
n.stopIPC()
|
n.stopIPC()
|
||||||
n.stopInProc()
|
n.stopInProc()
|
||||||
@ -381,7 +410,6 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors
|
|||||||
n.httpEndpoint = endpoint
|
n.httpEndpoint = endpoint
|
||||||
n.httpListener = listener
|
n.httpListener = listener
|
||||||
n.httpHandler = handler
|
n.httpHandler = handler
|
||||||
n.httpCors = cors
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -436,7 +464,6 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig
|
|||||||
n.wsEndpoint = endpoint
|
n.wsEndpoint = endpoint
|
||||||
n.wsListener = listener
|
n.wsListener = listener
|
||||||
n.wsHandler = handler
|
n.wsHandler = handler
|
||||||
n.wsOrigins = wsOrigins
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -465,12 +492,12 @@ func (n *Node) Stop() error {
|
|||||||
if n.server == nil {
|
if n.server == nil {
|
||||||
return ErrNodeStopped
|
return ErrNodeStopped
|
||||||
}
|
}
|
||||||
// Otherwise terminate the API, all services and the P2P server too
|
|
||||||
|
// Terminate the API, services and the p2p server.
|
||||||
n.stopWS()
|
n.stopWS()
|
||||||
n.stopHTTP()
|
n.stopHTTP()
|
||||||
n.stopIPC()
|
n.stopIPC()
|
||||||
n.rpcAPIs = nil
|
n.rpcAPIs = nil
|
||||||
|
|
||||||
failure := &StopError{
|
failure := &StopError{
|
||||||
Services: make(map[reflect.Type]error),
|
Services: make(map[reflect.Type]error),
|
||||||
}
|
}
|
||||||
@ -480,9 +507,16 @@ func (n *Node) Stop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
n.server.Stop()
|
n.server.Stop()
|
||||||
|
|
||||||
n.services = nil
|
n.services = nil
|
||||||
n.server = nil
|
n.server = nil
|
||||||
|
|
||||||
|
// Release instance directory lock.
|
||||||
|
if n.instanceDirLock != nil {
|
||||||
|
n.instanceDirLock.Close()
|
||||||
|
n.instanceDirLock = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unblock n.Wait
|
||||||
close(n.stop)
|
close(n.stop)
|
||||||
|
|
||||||
// Remove the keystore if it was created ephemerally.
|
// 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.
|
// DataDir retrieves the current datadir used by the protocol stack.
|
||||||
func (n *Node) DataDir() string {
|
func (n *Node) DataDir() string {
|
||||||
return n.datadir
|
return n.config.DataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountManager retrieves the account manager used by the protocol stack.
|
// AccountManager retrieves the account manager used by the protocol stack.
|
||||||
@ -595,6 +629,21 @@ func (n *Node) EventMux() *event.TypeMux {
|
|||||||
return n.eventmux
|
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.
|
// apis returns the collection of RPC descriptors this node offers.
|
||||||
func (n *Node) apis() []rpc.API {
|
func (n *Node) apis() []rpc.API {
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
@ -31,7 +30,7 @@ import (
|
|||||||
// the protocol stack, that is passed to all constructors to be optionally used;
|
// the protocol stack, that is passed to all constructors to be optionally used;
|
||||||
// as well as utility methods to operate on the service environment.
|
// as well as utility methods to operate on the service environment.
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
datadir string // Data directory for protocol persistence
|
config *Config
|
||||||
services map[reflect.Type]Service // Index of the already constructed services
|
services map[reflect.Type]Service // Index of the already constructed services
|
||||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||||
AccountManager *accounts.Manager // Account manager created by the node.
|
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
|
// 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.
|
// node is an ephemeral one, a memory database is returned.
|
||||||
func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int) (ethdb.Database, error) {
|
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.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.
|
// Service retrieves a currently running service registered of a specific type.
|
||||||
@ -64,10 +63,12 @@ type ServiceConstructor func(ctx *ServiceContext) (Service, error)
|
|||||||
// Service is an individual protocol that can be registered into a node.
|
// Service is an individual protocol that can be registered into a node.
|
||||||
//
|
//
|
||||||
// Notes:
|
// Notes:
|
||||||
// - Service life-cycle management is delegated to the node. The service is
|
//
|
||||||
// allowed to initialize itself upon creation, but no goroutines should be
|
// • Service life-cycle management is delegated to the node. The service is allowed to
|
||||||
// spun up outside of the Start method.
|
// initialize itself upon creation, but no goroutines should be spun up outside of the
|
||||||
// - Restart logic is not required as the node will create a fresh instance
|
// Start method.
|
||||||
|
//
|
||||||
|
// • Restart logic is not required as the node will create a fresh instance
|
||||||
// every time a service is started.
|
// every time a service is started.
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// Protocols retrieves the P2P protocols the service wishes to start.
|
// Protocols retrieves the P2P protocols the service wishes to start.
|
||||||
|
@ -38,18 +38,18 @@ func TestContextDatabases(t *testing.T) {
|
|||||||
t.Fatalf("non-created database already exists")
|
t.Fatalf("non-created database already exists")
|
||||||
}
|
}
|
||||||
// Request the opening/creation of a database and ensure it persists to disk
|
// 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)
|
db, err := ctx.OpenDatabase("persistent", 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open persistent database: %v", err)
|
t.Fatalf("failed to open persistent database: %v", err)
|
||||||
}
|
}
|
||||||
db.Close()
|
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)
|
t.Fatalf("persistent database doesn't exists: %v", err)
|
||||||
}
|
}
|
||||||
// Request th opening/creation of an ephemeral database and ensure it's not persisted
|
// 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)
|
db, err = ctx.OpenDatabase("ephemeral", 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to open ephemeral database: %v", err)
|
t.Fatalf("failed to open ephemeral database: %v", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user