mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 21:56:43 +00:00
node: Make IPC endpoint more generic
This commit is contained in:
parent
c75d3b0ede
commit
73c6f4b9e6
9 changed files with 175 additions and 94 deletions
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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:") {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
`)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
127
node/node.go
127
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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue