added attach over ipc command
This commit is contained in:
parent
36a6b16a3b
commit
f202563777
@ -26,6 +26,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/docserver"
|
"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/api"
|
||||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||||
"github.com/ethereum/go-ethereum/rpc/comms"
|
"github.com/ethereum/go-ethereum/rpc/comms"
|
||||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
|
||||||
"github.com/ethereum/go-ethereum/xeth"
|
"github.com/ethereum/go-ethereum/xeth"
|
||||||
"github.com/peterh/liner"
|
"github.com/peterh/liner"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
@ -137,6 +138,40 @@ func apiWordCompleter(line string, pos int) (head string, completions []string,
|
|||||||
return begin, completionWords, end
|
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 {
|
func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
|
||||||
js := &jsre{ethereum: ethereum, ps1: "> "}
|
js := &jsre{ethereum: ethereum, ps1: "> "}
|
||||||
// set default cors domain used by startRpc from CLI flag
|
// 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() {
|
func (self *jsre) loadAutoCompletion() {
|
||||||
if modules, err := self.suportedApis(); err == nil {
|
if modules, err := self.supportedApis(); err == nil {
|
||||||
loadedModulesMethods = make(map[string][]string)
|
loadedModulesMethods = make(map[string][]string)
|
||||||
for module, _ := range modules {
|
for module, _ := range modules {
|
||||||
loadedModulesMethods[module] = api.AutoCompletion[module]
|
loadedModulesMethods[module] = api.AutoCompletion[module]
|
||||||
@ -185,34 +220,33 @@ func (self *jsre) loadAutoCompletion() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *jsre) suportedApis() (map[string]string, error) {
|
// show summary of current geth instance
|
||||||
req := shared.Request{
|
func (self *jsre) welcome() {
|
||||||
Id: 1,
|
self.re.Eval(`console.log('instance: ' + web3.version.client);`)
|
||||||
Jsonrpc: "2.0",
|
self.re.Eval(`console.log(' datadir: ' + admin.datadir);`)
|
||||||
Method: "modules",
|
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() + ")");`)
|
||||||
|
|
||||||
err := self.client.Send(&req)
|
if modules, err := self.supportedApis(); err == nil {
|
||||||
if err != nil {
|
loadedModules := make([]string, 0)
|
||||||
return nil, err
|
for api, version := range modules {
|
||||||
}
|
loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version))
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
sort.Strings(loadedModules)
|
||||||
|
|
||||||
return nil, fmt.Errorf("Unable to determine supported API's")
|
self.re.Eval(fmt.Sprintf("var modules = '%s';", strings.Join(loadedModules, " ")))
|
||||||
|
self.re.Eval(`console.log(" modules: " + modules);`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *jsre) supportedApis() (map[string]string, error) {
|
||||||
|
return self.client.SupportedModules()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *jsre) apiBindings(f xeth.Frontend) error {
|
func (js *jsre) apiBindings(f xeth.Frontend) error {
|
||||||
apis, err := js.suportedApis()
|
apis, err := js.supportedApis()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -366,7 +400,12 @@ func (self *jsre) interactive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *jsre) withHistory(op func(*os.File)) {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("unable to open history file: %v\n", err)
|
fmt.Printf("unable to open history file: %v\n", err)
|
||||||
return
|
return
|
||||||
|
@ -204,6 +204,16 @@ nodes.
|
|||||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
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.
|
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
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()
|
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) {
|
func console(ctx *cli.Context) {
|
||||||
// Wrap the standard output with a colorified stream (windows)
|
// Wrap the standard output with a colorified stream (windows)
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
@ -323,6 +367,8 @@ func console(ctx *cli.Context) {
|
|||||||
true,
|
true,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
repl.welcome()
|
||||||
repl.interactive()
|
repl.interactive()
|
||||||
|
|
||||||
ethereum.Stop()
|
ethereum.Stop()
|
||||||
|
@ -4,12 +4,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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/rpc/api"
|
"github.com/ethereum/go-ethereum/rpc/api"
|
||||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -26,7 +28,7 @@ var (
|
|||||||
// List with API's which are offered over thr HTTP/RPC interface by default
|
// List with API's which are offered over thr HTTP/RPC interface by default
|
||||||
DefaultHttpRpcApis = strings.Join([]string{
|
DefaultHttpRpcApis = strings.Join([]string{
|
||||||
api.DbApiName, api.EthApiName, api.NetApiName, api.Web3ApiName,
|
api.DbApiName, api.EthApiName, api.NetApiName, api.Web3ApiName,
|
||||||
}, ",")
|
}, ",")
|
||||||
)
|
)
|
||||||
|
|
||||||
type EthereumClient interface {
|
type EthereumClient interface {
|
||||||
@ -36,6 +38,8 @@ type EthereumClient interface {
|
|||||||
Send(interface{}) error
|
Send(interface{}) error
|
||||||
// Receive response
|
// Receive response
|
||||||
Recv() (interface{}, error)
|
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) {
|
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")
|
||||||
|
}
|
||||||
|
@ -64,7 +64,6 @@ func StopHttp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type httpClient struct {
|
type httpClient struct {
|
||||||
codec codec.ApiCoder
|
codec codec.ApiCoder
|
||||||
}
|
}
|
||||||
@ -86,4 +85,4 @@ func (self *httpClient) Send(req interface{}) error {
|
|||||||
|
|
||||||
func (self *httpClient) Recv() (interface{}, error) {
|
func (self *httpClient) Recv() (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -51,3 +51,21 @@ func (self *InProcClient) Send(req interface{}) error {
|
|||||||
func (self *InProcClient) Recv() (interface{}, error) {
|
func (self *InProcClient) Recv() (interface{}, error) {
|
||||||
return self.lastRes, self.lastErr
|
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")
|
||||||
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package comms
|
package comms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rpc/api"
|
"github.com/ethereum/go-ethereum/rpc/api"
|
||||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||||
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IpcConfig struct {
|
type IpcConfig struct {
|
||||||
@ -10,19 +15,74 @@ type IpcConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ipcClient struct {
|
type ipcClient struct {
|
||||||
codec codec.ApiCoder
|
endpoint string
|
||||||
|
codec codec.Codec
|
||||||
|
coder codec.ApiCoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ipcClient) Close() {
|
func (self *ipcClient) Close() {
|
||||||
self.codec.Close()
|
self.coder.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ipcClient) Send(req interface{}) error {
|
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) {
|
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
|
// Create a new IPC client, UNIX domain socket on posix, named pipe on Windows
|
||||||
|
@ -18,7 +18,17 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
|
|||||||
return nil, err
|
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 {
|
func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error {
|
||||||
|
@ -641,7 +641,15 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
|
|||||||
return nil, err
|
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 {
|
func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error {
|
||||||
|
Loading…
Reference in New Issue
Block a user