diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index e0e549e124..20bf7416a1 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -59,6 +59,7 @@ Welcome to the Geth JavaScript console! instance: Geth/v{{gethver}}/{{goos}}/{{gover}} coinbase: {{.Etherbase}} + network: mainnet at block: 0 ({{niltime}}) datadir: {{.Datadir}} modules:{{range apis}} {{.}}:1.0{{end}} @@ -153,6 +154,7 @@ Welcome to the Geth JavaScript console! instance: Geth/v{{gethver}}/{{goos}}/{{gover}} coinbase: {{etherbase}} + network: mainnet at block: 0 ({{niltime}}){{if ipc}} datadir: {{datadir}}{{end}} modules:{{range apis}} {{.}}:1.0{{end}} diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 2e07e94260..73591cee4b 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -95,7 +95,7 @@ func main() { func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) { // Create a networkless protocol stack stack, err := node.New(&node.Config{ - IPCPath: node.DefaultIPCEndpoint(), + IPCPath: []string{node.DefaultIPCEndpoint()}, HTTPHost: common.DefaultHTTPHost, HTTPPort: common.DefaultHTTPPort, HTTPModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"}, diff --git a/cmd/utils/client.go b/cmd/utils/client.go index ec72a1a4b3..7d9badb872 100644 --- a/cmd/utils/client.go +++ b/cmd/utils/client.go @@ -18,6 +18,7 @@ package utils import ( "fmt" + "runtime" "strings" "github.com/codegangsta/cli" @@ -40,6 +41,11 @@ func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) { // endpoint. It must start with either `ipc:` or `rpc:` (HTTP). func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) { if strings.HasPrefix(endpoint, "ipc:") { + if runtime.GOOS == "windows" && !strings.HasPrefix(endpoint, `ipc:\\.\pipe\`) { + // user supplied named pipe name, e.g: attach "namedpipename" instead + // of "attach ipc:\\.\pipe\namedpipename". + endpoint = `ipc:\\.\pipe\` + endpoint[4:] + } return rpc.NewIPCClient(endpoint[4:]) } if strings.HasPrefix(endpoint, "rpc:") { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c476e1c779..c5d69f7a2f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -271,8 +271,8 @@ var ( } IPCPathFlag = DirectoryFlag{ Name: "ipcpath", - Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)", - Value: DirectoryString{common.DefaultIPCSocket}, + Usage: "IPC endpoint location (explicit paths escape it)", + Value: DirectoryString{common.DefaultIPCEndpoint()}, } WSEnabledFlag = cli.BoolFlag{ Name: "ws", @@ -418,13 +418,27 @@ func MakeKeyStoreDir(datadir string, ctx *cli.Context) string { return filepath.Join(datadir, "keystore") } -// MakeIPCPath creates an IPC path configuration from the set command line flags, -// returning an empty string if IPC was explicitly disabled, or the set path. -func MakeIPCPath(ctx *cli.Context) string { +// MakeIPCPaths creates an IPC path configuration from the set command line flags, +// returning an empty slice if IPC was explicitly disabled, or the paths to open +// the IPC endpoint(s). +func MakeIPCPaths(ctx *cli.Context) []string { if ctx.GlobalBool(IPCDisabledFlag.Name) { - return "" + return []string{} } - return ctx.GlobalString(IPCPathFlag.Name) + + // user overruled default location + if ctx.GlobalIsSet(IPCPathFlag.Name) { + return []string{ctx.GlobalString(IPCPathFlag.Name)} + } + + // return the old geth specific path and new more general default path. + // This is a temporary situation, the old path will be removed after a couple + // of weeks. This gives clients time to update and users the ability to use + // the latest geth version with default IPC setting as they are used to. + if runtime.GOOS == "windows" { + return []string{`\\.\pipe\geth.ipc`, ctx.GlobalString(IPCPathFlag.Name)} + } + return []string{"geth.ipc", ctx.GlobalString(IPCPathFlag.Name)} } // MakeNodeKey creates a node key from set command line flags, either loading it @@ -664,7 +678,7 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte, NAT: MakeNAT(ctx), MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), - IPCPath: MakeIPCPath(ctx), + IPCPath: MakeIPCPaths(ctx), HTTPHost: MakeHTTPRpcHost(ctx), HTTPPort: ctx.GlobalInt(RPCPortFlag.Name), HTTPCors: ctx.GlobalString(RPCCORSDomainFlag.Name), diff --git a/common/defaults.go b/common/defaults.go index 8a136fa801..6cdc4f4f3c 100644 --- a/common/defaults.go +++ b/common/defaults.go @@ -19,14 +19,16 @@ package common import ( "path/filepath" "runtime" + "os" ) const ( - DefaultIPCSocket = "geth.ipc" // Default (relative) name of the IPC RPC socket - DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server - DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server - DefaultWSHost = "localhost" // Default host interface for the websocket RPC server - DefaultWSPort = 8546 // Default TCP port for the websocket RPC server + DefaultIPCSocket = "ipc.sock" // Default (relative) name of the IPC RPC unix socket + DefaultIPCNamedPipe = `\\.\pipe\ethereum` // Default name of the IPC RPC named pipe + DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server + DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server + DefaultWSHost = "localhost" // Default host interface for the websocket RPC server + DefaultWSPort = 8546 // Default TCP port for the websocket RPC server ) // DefaultDataDir is the default data directory to use for the databases and other @@ -46,3 +48,27 @@ func DefaultDataDir() string { // As we cannot guess a stable location, return empty and handle later return "" } + +// DefaultIPCEndpoint returns the default location where to open the IPC endpoint. +// +// On Windows this is DefaultIPCNamedPipe. +// On OSX this is DefaultDataDir/DefaultIPCSocket. +// Else this is $ENV{XDG_RUNTIME_DIR}/ethereum/DefaultIPCSocket, if $ENV{XDG_RUNTIME_DIR} is not set, +// or the ethereum sub directory could not be created this is DefaultDataDir/DefaultIPCSocket. +func DefaultIPCEndpoint() string { + if runtime.GOOS == "windows" { + return DefaultIPCNamedPipe + } + if runtime.GOOS == "darwin" { + return filepath.Join(DefaultDataDir(), DefaultIPCSocket) + } + if dir := os.Getenv("XDG_RUNTIME_DIR"); dir != "" { + dir = filepath.Join(dir, "ethereum") + // create "ethereum" subdirectory if it doesn't exist + if err := os.MkdirAll(dir, 0700); err == nil { + return filepath.Join(dir, DefaultIPCSocket) + } + } + + return filepath.Join(DefaultDataDir(), DefaultIPCSocket) +} diff --git a/console/console.go b/console/console.go index ab0c1ea585..0dff4b1376 100644 --- a/console/console.go +++ b/console/console.go @@ -247,8 +247,17 @@ func (c *Console) Welcome() { // Print some generic Geth metadata fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n") c.jsre.Run(` + var _network = 'private'; + switch (eth.getBlock(0).hash.toLowerCase()) { + case '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3': + _network = 'mainnet'; break + case '0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303': + _network = 'testnet'; break + } + console.log("instance: " + web3.version.node); console.log("coinbase: " + eth.coinbase); + console.log(" network: " + _network); console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")"); console.log(" datadir: " + admin.datadir); `) diff --git a/node/config.go b/node/config.go index bc9fec618c..b11eb7b3b4 100644 --- a/node/config.go +++ b/node/config.go @@ -53,11 +53,12 @@ type Config struct { // in memory. DataDir string - // IPCPath is the requested location to place the IPC endpoint. If the path is - // a simple file name, it is placed inside the data directory (or on the root + // IPCPath is the requested location to place the IPC endpoint(s). If the path + // is a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or - // relative), then that specific path is enforced. An empty path disables IPC. - IPCPath string + // relative), then that specific path is enforced. A nil or empty slice + // disables IPC. + IPCPath []string // This field should be a valid secp256k1 private key that will be used for both // remote peer identification as well as network traffic encryption. If no key @@ -138,35 +139,39 @@ type Config struct { WSModules []string } -// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into +// IPCEndpoints resolves the IPC endpoints based on a configured value, taking into // account the set data folders as well as the designated platform we're currently // running on. -func (c *Config) IPCEndpoint() string { - // Short circuit if IPC has not been enabled - if c.IPCPath == "" { - return "" - } - // On windows we can only use plain top-level pipes - if runtime.GOOS == "windows" { - if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) { - return c.IPCPath +func (c *Config) IPCEndpoints() []string { + var endpoints []string + for _, endpoint := range c.IPCPath { + if endpoint == "" { + endpoints = append(endpoints, "") + } else if runtime.GOOS == "windows" { + // On windows we can only use plain top-level pipes + if strings.HasPrefix(endpoint, `\\.\pipe\`) { + endpoints = append(endpoints, endpoint) + } else { + endpoints = append(endpoints, `\\.\pipe\`+endpoint) + } + } else if filepath.Base(endpoint) == endpoint { // Resolve names into the data directory full paths otherwise + if c.DataDir == "" { + endpoints = append(endpoints, filepath.Join(os.TempDir(), endpoint)) + } else { + endpoints = append(endpoints, filepath.Join(c.DataDir, endpoint)) + } + } else { + endpoints = append(endpoints, endpoint) } - return `\\.\pipe\` + c.IPCPath } - // Resolve names into the data directory full paths otherwise - if filepath.Base(c.IPCPath) == c.IPCPath { - if c.DataDir == "" { - return filepath.Join(os.TempDir(), c.IPCPath) - } - return filepath.Join(c.DataDir, c.IPCPath) - } - return c.IPCPath + + return endpoints } // DefaultIPCEndpoint returns the IPC path used by default. func DefaultIPCEndpoint() string { - config := &Config{DataDir: common.DefaultDataDir(), IPCPath: common.DefaultIPCSocket} - return config.IPCEndpoint() + config := &Config{DataDir: common.DefaultDataDir(), IPCPath: []string{common.DefaultIPCEndpoint()}} + return config.IPCEndpoints()[0] } // HTTPEndpoint resolves an HTTP endpoint based on the configured host interface diff --git a/node/config_test.go b/node/config_test.go index 45a54d184a..8fbd884a3a 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -85,7 +85,7 @@ func TestIPCPathResolution(t *testing.T) { for i, test := range tests { // Only run when platform/test match if (runtime.GOOS == "windows") == test.Windows { - if endpoint := (&Config{DataDir: test.DataDir, IPCPath: test.IPCPath}).IPCEndpoint(); endpoint != test.Endpoint { + if endpoint := (&Config{DataDir: test.DataDir, IPCPath: []string{test.IPCPath}}).IPCEndpoints()[0]; endpoint != test.Endpoint { t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) } } diff --git a/node/node.go b/node/node.go index 1f517a027e..dce0b42324 100644 --- a/node/node.go +++ b/node/node.go @@ -43,6 +43,17 @@ var ( datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} ) +type ipcService struct { + endpoint string // endpoints to listen at (empty = IPC disabled) + listener net.Listener // RPC listener sockets to serve API requests + handler *rpc.Server // RPC request handlers to process the API requests +} + +func (s *ipcService) stop() { + s.listener.Close() + s.handler.Stop() +} + // Node represents a P2P node into which arbitrary (uniquely typed) services might // be registered. type Node struct { @@ -58,9 +69,7 @@ type Node struct { rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled) - ipcListener net.Listener // IPC RPC listener socket to serve API requests - ipcHandler *rpc.Server // IPC RPC request handler to process the API requests + ipcServices []*ipcService // IPC services httpHost string // HTTP hostname httpPort int // HTTP post @@ -95,6 +104,12 @@ func New(conf *Config) (*Node, error) { if conf.DataDir != "" { nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase) } + + ipcServices := make([]*ipcService, len(conf.IPCEndpoints())) + for i, endpoint := range conf.IPCEndpoints() { + ipcServices[i] = &ipcService{endpoint: endpoint} + } + return &Node{ datadir: conf.DataDir, serverConfig: p2p.Config{ @@ -113,7 +128,7 @@ func New(conf *Config) (*Node, error) { MaxPendingPeers: conf.MaxPendingPeers, }, serviceFuncs: []ServiceConstructor{}, - ipcEndpoint: conf.IPCEndpoint(), + ipcServices: ipcServices, httpHost: conf.HTTPHost, httpPort: conf.HTTPPort, httpEndpoint: conf.HTTPEndpoint(), @@ -272,64 +287,65 @@ func (n *Node) stopInProc() { // startIPC initializes and starts the IPC RPC endpoint. func (n *Node) startIPC(apis []rpc.API) error { - // Short circuit if the IPC endpoint isn't being exposed - if n.ipcEndpoint == "" { - return nil - } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + for i, _ := range n.ipcServices { + service := n.ipcServices[i] + + // Register all the APIs exposed by the services + handler := rpc.NewServer() + + for _, api := range apis { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + glog.V(logger.Debug).Infof("IPC registered %T under '%s' on '%s'", api.Service, api.Namespace, service.endpoint) + } + + // All APIs registered, start the IPC listener + var ( + listener net.Listener + err error + ) + + if listener, err = rpc.CreateIPCListener(service.endpoint); err != nil { return err } - glog.V(logger.Debug).Infof("IPC registered %T under '%s'", api.Service, api.Namespace) - } - // All APIs registered, start the IPC listener - var ( - listener net.Listener - err error - ) - if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil { - return err - } - go func() { - glog.V(logger.Info).Infof("IPC endpoint opened: %s", n.ipcEndpoint) - for { - conn, err := listener.Accept() - if err != nil { - // Terminate if the listener was closed - n.lock.RLock() - closed := n.ipcListener == nil - n.lock.RUnlock() - if closed { - return + go func() { + glog.V(logger.Info).Infof("IPC endpoint opened: %s", service.endpoint) + + for { + conn, err := listener.Accept() + if err != nil { + // Terminate if the listener was closed + n.lock.RLock() + closed := service.listener == nil + n.lock.RUnlock() + if closed { + return + } + // Not closed, just some error; report and continue + glog.V(logger.Error).Infof("IPC accept failed: %v", err) + continue } - // Not closed, just some error; report and continue - glog.V(logger.Error).Infof("IPC accept failed: %v", err) - continue + go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) } - go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) - } - }() - // All listeners booted successfully - n.ipcListener = listener - n.ipcHandler = handler + }() + + service.listener = listener + service.handler = handler + } return nil } // stopIPC terminates the IPC RPC endpoint. func (n *Node) stopIPC() { - if n.ipcListener != nil { - n.ipcListener.Close() - n.ipcListener = nil - - glog.V(logger.Info).Infof("IPC endpoint closed: %s", n.ipcEndpoint) - } - if n.ipcHandler != nil { - n.ipcHandler.Stop() - n.ipcHandler = nil + for _, service := range n.ipcServices { + service.listener.Close() + service.listener = nil + service.handler.Stop() + service.handler = nil + glog.V(logger.Info).Infof("IPC endpoint closed: %s", service.endpoint) } } @@ -550,9 +566,12 @@ func (n *Node) DataDir() string { return n.datadir } -// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. -func (n *Node) IPCEndpoint() string { - return n.ipcEndpoint +// IPCEndpoint retrieves the current IPC endpoints used by the protocol stack. +func (n *Node) IPCEndpoint() (endpoints []string) { + for _, service := range n.ipcServices { + endpoints = append(endpoints, service.endpoint) + } + return } // HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.