aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBas van Kervel <bas@ethdev.com>2015-06-19 00:23:13 +0800
committerBas van Kervel <bas@ethdev.com>2015-06-22 15:17:09 +0800
commitf20256377731097c9478ede750efffd46d83b494 (patch)
tree25ea2b0de5382ed25dde5ff2021214625f3e9865
parent36a6b16a3bec15131318ebed1c8c2f9204a5a328 (diff)
downloaddexon-f20256377731097c9478ede750efffd46d83b494.tar
dexon-f20256377731097c9478ede750efffd46d83b494.tar.gz
dexon-f20256377731097c9478ede750efffd46d83b494.tar.bz2
dexon-f20256377731097c9478ede750efffd46d83b494.tar.lz
dexon-f20256377731097c9478ede750efffd46d83b494.tar.xz
dexon-f20256377731097c9478ede750efffd46d83b494.tar.zst
dexon-f20256377731097c9478ede750efffd46d83b494.zip
added attach over ipc command
-rw-r--r--cmd/geth/js.go89
-rw-r--r--cmd/geth/main.go46
-rw-r--r--rpc/comms/comms.go27
-rw-r--r--rpc/comms/http.go3
-rw-r--r--rpc/comms/inproc.go18
-rw-r--r--rpc/comms/ipc.go68
-rw-r--r--rpc/comms/ipc_unix.go12
-rw-r--r--rpc/comms/ipc_windows.go10
8 files changed, 238 insertions, 35 deletions
diff --git a/cmd/geth/js.go b/cmd/geth/js.go
index bb5143170..38ef59583 100644
--- a/cmd/geth/js.go
+++ b/cmd/geth/js.go
@@ -26,6 +26,8 @@ import (
"path/filepath"
"strings"
+ "sort"
+
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/docserver"
@@ -36,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
- "github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/xeth"
"github.com/peterh/liner"
"github.com/robertkrimen/otto"
@@ -137,6 +138,40 @@ func apiWordCompleter(line string, pos int) (head string, completions []string,
return begin, completionWords, end
}
+func newLightweightJSRE(libPath string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
+ js := &jsre{ps1: "> "}
+ js.wait = make(chan *big.Int)
+ js.client = client
+
+ if f == nil {
+ f = js
+ }
+
+ // update state in separare forever blocks
+ js.re = re.New(libPath)
+ if err := js.apiBindings(f); err != nil {
+ utils.Fatalf("Unable to initialize console - %v", err)
+ }
+
+ if !liner.TerminalSupported() || !interactive {
+ js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
+ } else {
+ lr := liner.NewLiner()
+ //js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
+ lr.SetCtrlCAborts(true)
+ js.loadAutoCompletion()
+ lr.SetWordCompleter(apiWordCompleter)
+ lr.SetTabCompletionStyle(liner.TabPrints)
+ js.prompter = lr
+ js.atexit = func() {
+ js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
+ lr.Close()
+ close(js.wait)
+ }
+ }
+ return js
+}
+
func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
js := &jsre{ethereum: ethereum, ps1: "> "}
// set default cors domain used by startRpc from CLI flag
@@ -177,7 +212,7 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, client comms.Et
}
func (self *jsre) loadAutoCompletion() {
- if modules, err := self.suportedApis(); err == nil {
+ if modules, err := self.supportedApis(); err == nil {
loadedModulesMethods = make(map[string][]string)
for module, _ := range modules {
loadedModulesMethods[module] = api.AutoCompletion[module]
@@ -185,34 +220,33 @@ func (self *jsre) loadAutoCompletion() {
}
}
-func (self *jsre) suportedApis() (map[string]string, error) {
- req := shared.Request{
- Id: 1,
- Jsonrpc: "2.0",
- Method: "modules",
- }
-
- err := self.client.Send(&req)
- if err != nil {
- return nil, err
- }
-
- res, err := self.client.Recv()
- if err != nil {
- return nil, err
- }
-
- if sucRes, ok := res.(map[string]string); ok {
- if err == nil {
- return sucRes, nil
+// show summary of current geth instance
+func (self *jsre) welcome() {
+ self.re.Eval(`console.log('instance: ' + web3.version.client);`)
+ self.re.Eval(`console.log(' datadir: ' + admin.datadir);`)
+ self.re.Eval(`console.log("coinbase: " + eth.coinbase);`)
+ self.re.Eval(`var lastBlockTimestamp = 1000 * eth.getBlock(eth.blockNumber).timestamp`)
+ self.re.Eval(`console.log("at block: " + eth.blockNumber + " (" + new Date(lastBlockTimestamp).toLocaleDateString()
+ + " " + new Date(lastBlockTimestamp).toLocaleTimeString() + ")");`)
+
+ if modules, err := self.supportedApis(); err == nil {
+ loadedModules := make([]string, 0)
+ for api, version := range modules {
+ loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version))
}
+ sort.Strings(loadedModules)
+
+ self.re.Eval(fmt.Sprintf("var modules = '%s';", strings.Join(loadedModules, " ")))
+ self.re.Eval(`console.log(" modules: " + modules);`)
}
+}
- return nil, fmt.Errorf("Unable to determine supported API's")
+func (self *jsre) supportedApis() (map[string]string, error) {
+ return self.client.SupportedModules()
}
func (js *jsre) apiBindings(f xeth.Frontend) error {
- apis, err := js.suportedApis()
+ apis, err := js.supportedApis()
if err != nil {
return err
}
@@ -366,7 +400,12 @@ func (self *jsre) interactive() {
}
func (self *jsre) withHistory(op func(*os.File)) {
- hist, err := os.OpenFile(filepath.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
+ datadir := common.DefaultDataDir()
+ if self.ethereum != nil {
+ datadir = self.ethereum.DataDir
+ }
+
+ hist, err := os.OpenFile(filepath.Join(datadir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
fmt.Printf("unable to open history file: %v\n", err)
return
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 5c4c33cea..f1e8ace3d 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -204,6 +204,16 @@ nodes.
The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API.
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
+`},
+ {
+ Action: attach,
+ Name: "attach",
+ Usage: `Geth Console: interactive JavaScript environment`,
+ Description: `
+The Geth console is an interactive shell for the JavaScript runtime environment
+which exposes a node admin interface as well as the Ðapp JavaScript API.
+See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
+This command allows to open a console on a running geth node.
`,
},
{
@@ -297,6 +307,40 @@ func run(ctx *cli.Context) {
ethereum.WaitForShutdown()
}
+func attach(ctx *cli.Context) {
+ // Wrap the standard output with a colorified stream (windows)
+ if isatty.IsTerminal(os.Stdout.Fd()) {
+ if pr, pw, err := os.Pipe(); err == nil {
+ go io.Copy(colorable.NewColorableStdout(), pr)
+ os.Stdout = pw
+ }
+ }
+
+ var client comms.EthereumClient
+ var err error
+ if ctx.Args().Present() {
+ client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON)
+ } else {
+ cfg := comms.IpcConfig{
+ Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name),
+ }
+ client, err = comms.NewIpcClient(cfg, codec.JSON)
+ }
+
+ if err != nil {
+ utils.Fatalf("Unable to attach to geth node - %v", err)
+ }
+
+ repl := newLightweightJSRE(
+ ctx.String(utils.JSpathFlag.Name),
+ client,
+ true,
+ nil)
+
+ repl.welcome()
+ repl.interactive()
+}
+
func console(ctx *cli.Context) {
// Wrap the standard output with a colorified stream (windows)
if isatty.IsTerminal(os.Stdout.Fd()) {
@@ -323,6 +367,8 @@ func console(ctx *cli.Context) {
true,
nil,
)
+
+ repl.welcome()
repl.interactive()
ethereum.Stop()
diff --git a/rpc/comms/comms.go b/rpc/comms/comms.go
index 050e7b4e2..7aa94b1ea 100644
--- a/rpc/comms/comms.go
+++ b/rpc/comms/comms.go
@@ -4,12 +4,14 @@ import (
"io"
"net"
+ "fmt"
+ "strings"
+
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/shared"
- "strings"
)
const (
@@ -26,7 +28,7 @@ var (
// List with API's which are offered over thr HTTP/RPC interface by default
DefaultHttpRpcApis = strings.Join([]string{
api.DbApiName, api.EthApiName, api.NetApiName, api.Web3ApiName,
- }, ",")
+ }, ",")
)
type EthereumClient interface {
@@ -36,6 +38,8 @@ type EthereumClient interface {
Send(interface{}) error
// Receive response
Recv() (interface{}, error)
+ // List with modules this client supports
+ SupportedModules() (map[string]string, error)
}
func handle(conn net.Conn, api api.EthereumApi, c codec.Codec) {
@@ -64,3 +68,22 @@ func handle(conn net.Conn, api api.EthereumApi, c codec.Codec) {
}
}
}
+
+// Endpoint must be in the form of:
+// ${protocol}:${path}
+// e.g. ipc:/tmp/geth.ipc
+// rpc:localhost:8545
+func ClientFromEndpoint(endpoint string, c codec.Codec) (EthereumClient, error) {
+ if strings.HasPrefix(endpoint, "ipc:") {
+ cfg := IpcConfig{
+ Endpoint: endpoint[4:],
+ }
+ return NewIpcClient(cfg, codec.JSON)
+ }
+
+ if strings.HasPrefix(endpoint, "rpc:") {
+
+ }
+
+ return nil, fmt.Errorf("Invalid endpoint")
+}
diff --git a/rpc/comms/http.go b/rpc/comms/http.go
index c0ea2cc78..04630d937 100644
--- a/rpc/comms/http.go
+++ b/rpc/comms/http.go
@@ -64,7 +64,6 @@ func StopHttp() {
}
}
-
type httpClient struct {
codec codec.ApiCoder
}
@@ -86,4 +85,4 @@ func (self *httpClient) Send(req interface{}) error {
func (self *httpClient) Recv() (interface{}, error) {
return nil, nil
-} \ No newline at end of file
+}
diff --git a/rpc/comms/inproc.go b/rpc/comms/inproc.go
index 1fdbf8ace..b9c4e93d9 100644
--- a/rpc/comms/inproc.go
+++ b/rpc/comms/inproc.go
@@ -51,3 +51,21 @@ func (self *InProcClient) Send(req interface{}) error {
func (self *InProcClient) Recv() (interface{}, error) {
return self.lastRes, self.lastErr
}
+
+func (self *InProcClient) SupportedModules() (map[string]string, error) {
+ req := shared.Request{
+ Id: 1,
+ Jsonrpc: "2.0",
+ Method: "modules",
+ }
+
+ if res, err := self.api.Execute(&req); err == nil {
+ if result, ok := res.(map[string]string); ok {
+ return result, nil
+ }
+ } else {
+ return nil, err
+ }
+
+ return nil, fmt.Errorf("Invalid response")
+}
diff --git a/rpc/comms/ipc.go b/rpc/comms/ipc.go
index a07203803..7e7375eaf 100644
--- a/rpc/comms/ipc.go
+++ b/rpc/comms/ipc.go
@@ -1,8 +1,13 @@
package comms
import (
+ "fmt"
+ "net"
+
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
+ "github.com/ethereum/go-ethereum/rpc/shared"
+ "encoding/json"
)
type IpcConfig struct {
@@ -10,19 +15,74 @@ type IpcConfig struct {
}
type ipcClient struct {
- codec codec.ApiCoder
+ endpoint string
+ codec codec.Codec
+ coder codec.ApiCoder
}
func (self *ipcClient) Close() {
- self.codec.Close()
+ self.coder.Close()
}
func (self *ipcClient) Send(req interface{}) error {
- return self.codec.WriteResponse(req)
+ var err error
+ if r, ok := req.(*shared.Request); ok {
+ if err = self.coder.WriteResponse(r); err != nil {
+ if _, ok := err.(*net.OpError); ok { // connection lost, retry once
+ if err = self.reconnect(); err == nil {
+ err = self.coder.WriteResponse(r)
+ }
+ }
+ }
+ return err
+ }
+
+ return fmt.Errorf("Invalid request (%T)", req)
}
func (self *ipcClient) Recv() (interface{}, error) {
- return self.codec.ReadResponse()
+ res, err := self.coder.ReadResponse()
+ if err != nil {
+ return nil, err
+ }
+
+ if r, ok := res.(shared.SuccessResponse); ok {
+ return r.Result, nil
+ }
+
+ if r, ok := res.(shared.ErrorResponse); ok {
+ return r.Error, nil
+ }
+
+ return res, err
+}
+
+func (self *ipcClient) SupportedModules() (map[string]string, error) {
+ req := shared.Request{
+ Id: 1,
+ Jsonrpc: "2.0",
+ Method: "modules",
+ }
+
+ if err := self.coder.WriteResponse(req); err != nil {
+ return nil, err
+ }
+
+ res, err := self.coder.ReadResponse()
+ if err != nil {
+ return nil, err
+ }
+
+ if sucRes, ok := res.(shared.SuccessResponse); ok {
+ data, _ := json.Marshal(sucRes.Result)
+ modules := make(map[string]string)
+ err = json.Unmarshal(data, &modules)
+ if err == nil {
+ return modules, nil
+ }
+ }
+
+ return nil, fmt.Errorf("Invalid response")
}
// Create a new IPC client, UNIX domain socket on posix, named pipe on Windows
diff --git a/rpc/comms/ipc_unix.go b/rpc/comms/ipc_unix.go
index 131fb86f2..b5eec92db 100644
--- a/rpc/comms/ipc_unix.go
+++ b/rpc/comms/ipc_unix.go
@@ -18,7 +18,17 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
return nil, err
}
- return &ipcClient{codec.New(c)}, nil
+ return &ipcClient{cfg.Endpoint, codec, codec.New(c)}, nil
+}
+
+func (self *ipcClient) reconnect() error {
+ self.coder.Close()
+ c, err := net.DialUnix("unix", nil, &net.UnixAddr{self.endpoint, "unix"})
+ if err == nil {
+ self.coder = self.codec.New(c)
+ }
+
+ return err
}
func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error {
diff --git a/rpc/comms/ipc_windows.go b/rpc/comms/ipc_windows.go
index c48dfb7fb..08f79274a 100644
--- a/rpc/comms/ipc_windows.go
+++ b/rpc/comms/ipc_windows.go
@@ -641,7 +641,15 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
return nil, err
}
- return &ipcClient{codec.New(c)}, nil
+ return &ipcClient{cfg.Endpoint, codec, codec.New(c)}, nil
+}
+
+func (self *ipcClient) reconnect() error {
+ c, err := Dial(self.endpoint)
+ if err == nil {
+ self.coder = self.codec.New(c)
+ }
+ return err
}
func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error {