From 19b2640e89465c1c57f1bbea0274d52d97151f60 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Wed, 16 Dec 2015 10:58:01 +0100 Subject: rpc: migrated the RPC insterface to a new reflection based RPC layer --- cmd/geth/js.go | 172 ++++++++++++-------------- cmd/geth/js_test.go | 101 +++++++-------- cmd/geth/main.go | 51 ++++---- cmd/geth/monitorcmd.go | 47 ++++--- cmd/geth/usage.go | 8 +- cmd/gethrpctest/main.go | 53 ++++---- cmd/utils/api.go | 74 ----------- cmd/utils/client.go | 176 ++++++++++++++++++++++++++ cmd/utils/flags.go | 157 ++++++++++++----------- cmd/utils/jeth.go | 323 ++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 808 insertions(+), 354 deletions(-) delete mode 100644 cmd/utils/api.go create mode 100644 cmd/utils/client.go create mode 100644 cmd/utils/jeth.go (limited to 'cmd') diff --git a/cmd/geth/js.go b/cmd/geth/js.go index cdafab7fa..3d0251f08 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -24,23 +24,16 @@ import ( "os/signal" "path/filepath" "regexp" - "strings" - "sort" + "strings" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/natspec" "github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/eth" re "github.com/ethereum/go-ethereum/jsre" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - "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" ) @@ -79,82 +72,90 @@ func (r dumbterm) AppendHistory(string) {} type jsre struct { re *re.JSRE stack *node.Node - xeth *xeth.XEth wait chan *big.Int ps1 string atexit func() corsDomain string - client comms.EthereumClient + client rpc.Client prompter } var ( - loadedModulesMethods map[string][]string + loadedModulesMethods map[string][]string + autoCompleteStatement = "function _autocomplete(obj) {var results = []; for (var e in obj) { results.push(e); }; return results; }; _autocomplete(%s)" ) -func keywordCompleter(line string) []string { - results := make([]string, 0) - - if strings.Contains(line, ".") { - elements := strings.Split(line, ".") - if len(elements) == 2 { - module := elements[0] - partialMethod := elements[1] - if methods, found := loadedModulesMethods[module]; found { - for _, method := range methods { - if strings.HasPrefix(method, partialMethod) { // e.g. debug.se - results = append(results, module+"."+method) - } - } - } - } - } else { - for module, methods := range loadedModulesMethods { - if line == module { // user typed in full module name, show all methods - for _, method := range methods { - results = append(results, module+"."+method) +func keywordCompleter(jsre *jsre, line string) []string { + var results []string + parts := strings.Split(line, ".") + objRef := "this" + prefix := line + if len(parts) > 1 { + objRef = strings.Join(parts[0:len(parts) - 1], ".") + prefix = parts[len(parts) - 1] + } + + result, _ := jsre.re.Run(fmt.Sprintf(autoCompleteStatement, objRef)) + raw, _ := result.Export() + if keys, ok := raw.([]interface{}); ok { + for _, k := range keys { + if strings.HasPrefix(fmt.Sprintf("%s", k), prefix) { + if objRef == "this" { + results = append(results, fmt.Sprintf("%s", k)) + } else { + results = append(results, fmt.Sprintf("%s.%s", strings.Join(parts[:len(parts) - 1], "."), k)) } - } else if strings.HasPrefix(module, line) { // partial method name, e.g. admi - results = append(results, module) } } } - return results -} -func apiWordCompleter(line string, pos int) (head string, completions []string, tail string) { - if len(line) == 0 || pos == 0 { - return "", nil, "" + // e.g. web3 append dot since its an object + isObj, _ := jsre.re.Run(fmt.Sprintf("typeof(%s) === 'object'", line)) + if isObject, _ := isObj.ToBoolean(); isObject { + results = append(results, line + ".") } - i := 0 - for i = pos - 1; i > 0; i-- { - if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') { - continue + sort.Strings(results) + return results +} + +func apiWordCompleterWithContext(jsre *jsre) liner.WordCompleter { + completer := func(line string, pos int) (head string, completions []string, tail string) { + if len(line) == 0 || pos == 0 { + return "", nil, "" } - if i >= 3 && line[i] == '3' && line[i-3] == 'w' && line[i-2] == 'e' && line[i-1] == 'b' { - continue + + // chuck data to relevant part for autocompletion, e.g. in case of nested lines eth.getBalance(eth.coinb + i := 0 + for i = pos - 1; i > 0; i-- { + if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') { + continue + } + if i >= 3 && line[i] == '3' && line[i - 3] == 'w' && line[i - 2] == 'e' && line[i - 1] == 'b' { + continue + } + i += 1 + break } - i += 1 - break - } - begin := line[:i] - keyword := line[i:pos] - end := line[pos:] + begin := line[:i] + keyword := line[i:pos] + end := line[pos:] + + completionWords := keywordCompleter(jsre, keyword) + return begin, completionWords, end + } - completionWords := keywordCompleter(keyword) - return begin, completionWords, end + return completer } -func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir string, interactive bool) *jsre { +func newLightweightJSRE(docRoot string, client rpc.Client, datadir string, interactive bool) *jsre { js := &jsre{ps1: "> "} js.wait = make(chan *big.Int) js.client = client - // update state in separare forever blocks js.re = re.New(docRoot) - if err := js.apiBindings(js); err != nil { + if err := js.apiBindings(); err != nil { utils.Fatalf("Unable to initialize console - %v", err) } @@ -165,7 +166,7 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str js.withHistory(datadir, func(hist *os.File) { lr.ReadHistory(hist) }) lr.SetCtrlCAborts(true) js.loadAutoCompletion() - lr.SetWordCompleter(apiWordCompleter) + lr.SetWordCompleter(apiWordCompleterWithContext(js)) lr.SetTabCompletionStyle(liner.TabPrints) js.prompter = lr js.atexit = func() { @@ -177,25 +178,15 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str return js } -func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre { +func newJSRE(stack *node.Node, docRoot, corsDomain string, client rpc.Client, interactive bool) *jsre { js := &jsre{stack: stack, ps1: "> "} // set default cors domain used by startRpc from CLI flag js.corsDomain = corsDomain - if f == nil { - f = js - } - js.xeth = xeth.New(stack, f) - js.wait = js.xeth.UpdateState() + js.wait = make(chan *big.Int) js.client = client - if clt, ok := js.client.(*comms.InProcClient); ok { - if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, stack); err == nil { - clt.Initialize(api.Merge(offeredApis...)) - } - } - // update state in separare forever blocks js.re = re.New(docRoot) - if err := js.apiBindings(f); err != nil { + if err := js.apiBindings(); err != nil { utils.Fatalf("Unable to connect - %v", err) } @@ -206,7 +197,7 @@ func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.Ethereum js.withHistory(stack.DataDir(), func(hist *os.File) { lr.ReadHistory(hist) }) lr.SetCtrlCAborts(true) js.loadAutoCompletion() - lr.SetWordCompleter(apiWordCompleter) + lr.SetWordCompleter(apiWordCompleterWithContext(js)) lr.SetTabCompletionStyle(liner.TabPrints) js.prompter = lr js.atexit = func() { @@ -222,7 +213,7 @@ func (self *jsre) loadAutoCompletion() { if modules, err := self.supportedApis(); err == nil { loadedModulesMethods = make(map[string][]string) for module, _ := range modules { - loadedModulesMethods[module] = api.AutoCompletion[module] + loadedModulesMethods[module] = rpc.AutoCompletion[module] } } } @@ -258,7 +249,6 @@ func (self *jsre) welcome() { loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version)) } sort.Strings(loadedModules) - } } @@ -266,7 +256,7 @@ func (self *jsre) supportedApis() (map[string]string, error) { return self.client.SupportedModules() } -func (js *jsre) apiBindings(f xeth.Frontend) error { +func (js *jsre) apiBindings() error { apis, err := js.supportedApis() if err != nil { return err @@ -277,12 +267,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { apiNames = append(apiNames, a) } - apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.stack) - if err != nil { - utils.Fatalf("Unable to determine supported api's: %v", err) - } - - jeth := rpc.NewJeth(api.Merge(apiImpl...), js.re, js.client, f) + jeth := utils.NewJeth(js.re, js.client) js.re.Set("jeth", struct{}{}) t, _ := js.re.Get("jeth") jethObj := t.Object() @@ -313,14 +298,16 @@ func (js *jsre) apiBindings(f xeth.Frontend) error { // load only supported API's in javascript runtime shortcuts := "var eth = web3.eth; " for _, apiName := range apiNames { - if apiName == shared.Web3ApiName { - continue // manually mapped + if apiName == "web3" || apiName == "rpc" { + continue // manually mapped or ignore } - if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), api.Javascript(apiName)); err == nil { - shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName) - } else { - utils.Fatalf("Error loading %s.js: %v", apiName, err) + if jsFile, ok := rpc.WEB3Extensions[apiName]; ok { + if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), jsFile); err == nil { + shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName) + } else { + utils.Fatalf("Error loading %s.js: %v", apiName, err) + } } } @@ -375,14 +362,13 @@ func (self *jsre) ConfirmTransaction(tx string) bool { return false } // If natspec is enabled, ask for permission - if ethereum.NatSpec { - notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient()) - fmt.Println(notice) - answer, _ := self.Prompt("Confirm Transaction [y/n]") - return strings.HasPrefix(strings.Trim(answer, " "), "y") - } else { - return true + if ethereum.NatSpec && false /* disabled for now */ { + // notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient()) + // fmt.Println(notice) + // answer, _ := self.Prompt("Confirm Transaction [y/n]") + // return strings.HasPrefix(strings.Trim(answer, " "), "y") } + return true } func (self *jsre) UnlockAccount(addr []byte) bool { diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index ca636188f..19583c5ef 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -32,30 +32,27 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/httpclient" - "github.com/ethereum/go-ethereum/common/natspec" - "github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/ethereum/go-ethereum/cmd/utils" ) const ( testSolcPath = "" - solcVersion = "0.9.23" + solcVersion = "0.9.23" - testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" + testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" testBalance = "10000000000000000000" - // of empty string +// of empty string testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" ) var ( - versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) + versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")) testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` ) @@ -77,15 +74,16 @@ func (self *testjethre) UnlockAccount(acc []byte) bool { return true } -func (self *testjethre) ConfirmTransaction(tx string) bool { - var ethereum *eth.Ethereum - self.stack.Service(ðereum) - - if ethereum.NatSpec { - self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) - } - return true -} +// Temporary disabled while natspec hasn't been migrated +//func (self *testjethre) ConfirmTransaction(tx string) bool { +// var ethereum *eth.Ethereum +// self.stack.Service(ðereum) +// +// if ethereum.NatSpec { +// self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client) +// } +// return true +//} func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) { return testREPL(t, nil) @@ -118,7 +116,9 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod if config != nil { config(ethConf) } - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + return eth.New(ctx, ethConf) + }); err != nil { t.Fatalf("failed to register ethereum protocol: %v", err) } // Initialize all the keys for testing @@ -141,9 +141,10 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod stack.Service(ðereum) assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") - client := comms.NewInProcClient(codec.JSON) + //client := comms.NewInProcClient(codec.JSON) + client := utils.NewInProcRPCClient(stack) tf := &testjethre{client: ethereum.HTTPClient()} - repl := newJSRE(stack, assetPath, "", client, false, tf) + repl := newJSRE(stack, assetPath, "", client, false) tf.jsre = repl return tmp, tf, stack } @@ -166,8 +167,8 @@ func TestAccounts(t *testing.T) { defer node.Stop() defer os.RemoveAll(tmp) - checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) - checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`) + checkEvalJSON(t, repl, `eth.accounts`, `["` + testAddress + `"]`) + checkEvalJSON(t, repl, `eth.coinbase`, `"` + testAddress + `"`) val, err := repl.re.Run(`jeth.newAccount("password")`) if err != nil { t.Errorf("expected no error, got %v", err) @@ -177,7 +178,7 @@ func TestAccounts(t *testing.T) { t.Errorf("address not hex: %q", addr) } - checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`","`+addr+`"]`) + checkEvalJSON(t, repl, `eth.accounts`, `["` + testAddress + `","` + addr + `"]`) } @@ -205,13 +206,13 @@ func TestBlockChain(t *testing.T) { node.Service(ðereum) ethereum.BlockChain().Reset() - checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`) + checkEvalJSON(t, repl, `admin.exportChain(` + tmpfileq + `)`, `true`) if _, err := os.Stat(tmpfile); err != nil { t.Fatal(err) } // check import, verify that dumpBlock gives the same result. - checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`) + checkEvalJSON(t, repl, `admin.importChain(` + tmpfileq + `)`, `true`) checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport) } @@ -239,7 +240,7 @@ func TestCheckTestAccountBalance(t *testing.T) { defer os.RemoveAll(tmp) repl.re.Run(`primary = "` + testAddress + `"`) - checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`) + checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"` + testBalance + `"`) } func TestSignature(t *testing.T) { @@ -278,19 +279,20 @@ func TestContract(t *testing.T) { defer ethereum.Stop() defer os.RemoveAll(tmp) - reg := registrar.New(repl.xeth) - _, err := reg.SetGlobalRegistrar("", coinbase) - if err != nil { - t.Errorf("error setting HashReg: %v", err) - } - _, err = reg.SetHashReg("", coinbase) - if err != nil { - t.Errorf("error setting HashReg: %v", err) - } - _, err = reg.SetUrlHint("", coinbase) - if err != nil { - t.Errorf("error setting HashReg: %v", err) - } + // Temporary disabled while registrar isn't migrated + //reg := registrar.New(repl.xeth) + //_, err := reg.SetGlobalRegistrar("", coinbase) + //if err != nil { + // t.Errorf("error setting HashReg: %v", err) + //} + //_, err = reg.SetHashReg("", coinbase) + //if err != nil { + // t.Errorf("error setting HashReg: %v", err) + //} + //_, err = reg.SetUrlHint("", coinbase) + //if err != nil { + // t.Errorf("error setting HashReg: %v", err) + //} /* TODO: * lookup receipt and contract addresses by tx hash * name registration for HashReg and UrlHint addresses @@ -299,11 +301,11 @@ func TestContract(t *testing.T) { */ source := `contract test {\n` + - " /// @notice Will multiply `a` by 7." + `\n` + - ` function multiply(uint a) returns(uint d) {\n` + - ` return a * 7;\n` + - ` }\n` + - `}\n` + " /// @notice Will multiply `a` by 7." + `\n` + + ` function multiply(uint a) returns(uint d) {\n` + + ` return a * 7;\n` + + ` }\n` + + `}\n` if checkEvalJSON(t, repl, `admin.stopNatSpec()`, `true`) != nil { return @@ -313,10 +315,10 @@ func TestContract(t *testing.T) { if err != nil { t.Fatalf("%v", err) } - if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`) != nil { + if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"` + testAddress + `"`) != nil { return } - if checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`) != nil { + if checkEvalJSON(t, repl, `source = "` + source + `"`, `"` + source + `"`) != nil { return } @@ -394,7 +396,7 @@ multiply7 = Multiply7.at(contractaddress); var contentHash = `"0x86d2b7cf1e72e9a7a3f8d96601f0151742a2f780f1526414304fbe413dc7f9bd"` if sol != nil && solcVersion != sol.Version() { - modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`)) + modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"` + sol.Version() + `"`)) fmt.Printf("modified contractinfo:\n%s\n", modContractInfo) contentHash = `"` + common.ToHex(crypto.Sha3([]byte(modContractInfo))) + `"` } @@ -474,11 +476,12 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool { defer ethereum.StopMining() timer := time.NewTimer(100 * time.Second) - height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1)) + blockNr := ethereum.BlockChain().CurrentBlock().Number() + height := new(big.Int).Add(blockNr, big.NewInt(1)) repl.wait <- height select { case <-timer.C: - // if times out make sure the xeth loop does not block + // if times out make sure the xeth loop does not block go func() { select { case repl.wait <- nil: diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f2bb27552..e6d190914 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -40,8 +40,6 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" ) const ( @@ -263,11 +261,11 @@ See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console Name: "attach", Usage: `Geth Console: interactive JavaScript environment (connect to node)`, 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. -`, + 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. + `, }, { Action: execScripts, @@ -309,11 +307,15 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, - utils.RpcApiFlag, + utils.RPCApiFlag, + utils.WSEnabledFlag, + utils.WSListenAddrFlag, + utils.WSPortFlag, + utils.WSApiFlag, + utils.WSAllowedDomainsFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, - utils.IPCExperimental, utils.ExecFlag, utils.WhisperEnabledFlag, utils.DevModeFlag, @@ -392,20 +394,12 @@ func geth(ctx *cli.Context) { node.Wait() } +// attach will connect to a running geth instance attaching a JavaScript console and to it. func attach(ctx *cli.Context) { - 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: utils.IpcSocketPath(ctx), - } - client, err = comms.NewIpcClient(cfg, codec.JSON) - } - + // attach to a running geth instance + client, err := utils.NewRemoteRPCClient(ctx) if err != nil { - utils.Fatalf("Unable to attach to geth node - %v", err) + utils.Fatalf("Unable to attach to geth - %v", err) } repl := newLightweightJSRE( @@ -431,11 +425,12 @@ func console(ctx *cli.Context) { startNode(ctx, node) // Attach to the newly started node, and either execute script or become interactive - client := comms.NewInProcClient(codec.JSON) + client := utils.NewInProcRPCClient(node) + repl := newJSRE(node, ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - client, true, nil) + client, true) if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { repl.batch(script) @@ -454,11 +449,12 @@ func execScripts(ctx *cli.Context) { startNode(ctx, node) // Attach to the newly started node and execute the given scripts - client := comms.NewInProcClient(codec.JSON) + client := utils.NewInProcRPCClient(node) + repl := newJSRE(node, ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - client, false, nil) + client, false) for _, file := range ctx.Args() { repl.exec(file) @@ -517,6 +513,11 @@ func startNode(ctx *cli.Context, stack *node.Node) { utils.Fatalf("Failed to start RPC: %v", err) } } + if ctx.GlobalBool(utils.WSEnabledFlag.Name) { + if err := utils.StartWS(stack, ctx); err != nil { + utils.Fatalf("Failed to start WS: %v", err) + } + } if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil { utils.Fatalf("Failed to start mining: %v", err) diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index a45d29b8f..1d7bf3f6a 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -21,16 +21,15 @@ import ( "math" "reflect" "runtime" - "sort" "strings" "time" + "sort" + "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" "github.com/gizak/termui" ) @@ -70,20 +69,18 @@ to display multiple metrics simultaneously. // monitor starts a terminal UI based monitoring tool for the requested metrics. func monitor(ctx *cli.Context) { var ( - client comms.EthereumClient + client rpc.Client err error ) // Attach to an Ethereum node over IPC or RPC endpoint := ctx.String(monitorCommandAttachFlag.Name) - if client, err = comms.ClientFromEndpoint(endpoint, codec.JSON); err != nil { + if client, err = utils.NewRemoteRPCClientFromString(endpoint); err != nil { utils.Fatalf("Unable to attach to geth node: %v", err) } defer client.Close() - xeth := rpc.NewXeth(client) - // Retrieve all the available metrics and resolve the user pattens - metrics, err := retrieveMetrics(xeth) + metrics, err := retrieveMetrics(client) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } @@ -133,7 +130,7 @@ func monitor(ctx *cli.Context) { } termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) - refreshCharts(xeth, monitored, data, units, charts, ctx, footer) + refreshCharts(client, monitored, data, units, charts, ctx, footer) termui.Body.Align() termui.Render(termui.Body) @@ -154,7 +151,7 @@ func monitor(ctx *cli.Context) { termui.Render(termui.Body) } case <-refresh: - if refreshCharts(xeth, monitored, data, units, charts, ctx, footer) { + if refreshCharts(client, monitored, data, units, charts, ctx, footer) { termui.Body.Align() } termui.Render(termui.Body) @@ -164,8 +161,30 @@ func monitor(ctx *cli.Context) { // retrieveMetrics contacts the attached geth node and retrieves the entire set // of collected system metrics. -func retrieveMetrics(xeth *rpc.Xeth) (map[string]interface{}, error) { - return xeth.Call("debug_metrics", []interface{}{true}) +func retrieveMetrics(client rpc.Client) (map[string]interface{}, error) { + req := map[string]interface{}{ + "id": new(int64), + "method": "debug_metrics", + "jsonrpc": "2.0", + "params": []interface{}{true}, + } + + if err := client.Send(req); err != nil { + return nil, err + } + + var res rpc.JSONSuccessResponse + if err := client.Recv(&res); err != nil { + return nil, err + } + + if res.Result != nil { + if mets, ok := res.Result.(map[string]interface{}); ok { + return mets, nil + } + } + + return nil, fmt.Errorf("unable to retrieve metrics") } // resolveMetrics takes a list of input metric patterns, and resolves each to one @@ -253,8 +272,8 @@ func fetchMetric(metrics map[string]interface{}, metric string) float64 { // refreshCharts retrieves a next batch of metrics, and inserts all the new // values into the active datasets and charts -func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { - values, err := retrieveMetrics(xeth) +func refreshCharts(client rpc.Client, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { + values, err := retrieveMetrics(client) for i, metric := range metrics { if len(data) < 512 { data[i] = append([]float64{fetchMetric(values, metric)}, data[i]...) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 7a6ff704c..a9fce6418 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -87,7 +87,12 @@ var AppHelpFlagGroups = []flagGroup{ utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, - utils.RpcApiFlag, + utils.RPCApiFlag, + utils.WSEnabledFlag, + utils.WSListenAddrFlag, + utils.WSPortFlag, + utils.WSApiFlag, + utils.WSAllowedDomainsFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, @@ -158,7 +163,6 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ utils.WhisperEnabledFlag, utils.NatspecEnabledFlag, - utils.IPCExperimental, }, }, { diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index ae815c4a6..b4530ca51 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -26,8 +26,9 @@ import ( "path/filepath" "runtime" + "errors" + "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -35,13 +36,9 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc/api" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/whisper" - "github.com/ethereum/go-ethereum/xeth" ) const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" @@ -176,21 +173,25 @@ func RunTest(stack *node.Node, test *tests.BlockTest) error { // StartRPC initializes an RPC interface to the given protocol stack. func StartRPC(stack *node.Node) error { - config := comms.HttpConfig{ - ListenAddress: "127.0.0.1", - ListenPort: 8545, + /* + web3 := NewPublicWeb3API(stack) + server.RegisterName("web3", web3) + net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) + server.RegisterName("net", net) + */ + + for _, api := range stack.APIs() { + if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok { + _, err := adminApi.StartRPC("127.0.0.1", 8545, "", "admin,db,eth,debug,miner,net,shh,txpool,personal,web3") + return err + } } - xeth := xeth.New(stack, nil) - codec := codec.JSON - apis, err := api.ParseApiString(comms.DefaultHttpRpcApis, codec, xeth, stack) - if err != nil { - return err - } - return comms.StartHttp(config, codec, api.Merge(apis...)) + glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API") + return errors.New("Unable to start RPC-HTTP interface") } -// StartRPC initializes an IPC interface to the given protocol stack. +// StartIPC initializes an IPC interface to the given protocol stack. func StartIPC(stack *node.Node) error { var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { @@ -202,11 +203,7 @@ func StartIPC(stack *node.Node) error { endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc") } - config := comms.IpcConfig{ - Endpoint: endpoint, - } - - listener, err := comms.CreateListener(config) + listener, err := rpc.CreateIPCListener(endpoint) if err != nil { return err } @@ -217,16 +214,16 @@ func StartIPC(stack *node.Node) error { offered := stack.APIs() for _, api := range offered { server.RegisterName(api.Namespace, api.Service) - glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace) + glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) } - web3 := utils.NewPublicWeb3API(stack) - server.RegisterName("web3", web3) - net := utils.NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) - server.RegisterName("net", net) + //var ethereum *eth.Ethereum + //if err := stack.Service(ðereum); err != nil { + // return err + //} go func() { - glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint) + glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint) for { conn, err := listener.Accept() if err != nil { diff --git a/cmd/utils/api.go b/cmd/utils/api.go deleted file mode 100644 index 59f0dab74..000000000 --- a/cmd/utils/api.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2015 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 . - -package utils - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - rpc "github.com/ethereum/go-ethereum/rpc/v2" -) - -// PublicWeb3API offers helper utils -type PublicWeb3API struct { - stack *node.Node -} - -// NewPublicWeb3API creates a new Web3Service instance -func NewPublicWeb3API(stack *node.Node) *PublicWeb3API { - return &PublicWeb3API{stack} -} - -// ClientVersion returns the node name -func (s *PublicWeb3API) ClientVersion() string { - return s.stack.Server().Name -} - -// Sha3 applies the ethereum sha3 implementation on the input. -// It assumes the input is hex encoded. -func (s *PublicWeb3API) Sha3(input string) string { - return common.ToHex(crypto.Sha3(common.FromHex(input))) -} - -// PublicNetAPI offers network related RPC methods -type PublicNetAPI struct { - net *p2p.Server - networkVersion int -} - -// NewPublicNetAPI creates a new net api instance. -func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { - return &PublicNetAPI{net, networkVersion} -} - -// Listening returns an indication if the node is listening for network connections. -func (s *PublicNetAPI) Listening() bool { - return true // always listening -} - -// Peercount returns the number of connected peers -func (s *PublicNetAPI) PeerCount() *rpc.HexNumber { - return rpc.NewHexNumber(s.net.PeerCount()) -} - -// ProtocolVersion returns the current ethereum protocol version. -func (s *PublicNetAPI) Version() string { - return fmt.Sprintf("%d", s.networkVersion) -} diff --git a/cmd/utils/client.go b/cmd/utils/client.go new file mode 100644 index 000000000..bac456491 --- /dev/null +++ b/cmd/utils/client.go @@ -0,0 +1,176 @@ +// Copyright 2015 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 . + +package utils + +import ( + "encoding/json" + "fmt" + + "strings" + + "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" +) + +// NewInProcRPCClient will start a new RPC server for the given node and returns a client to interact with it. +func NewInProcRPCClient(stack *node.Node) *inProcClient { + server := rpc.NewServer() + + offered := stack.APIs() + for _, api := range offered { + server.RegisterName(api.Namespace, api.Service) + } + + web3 := node.NewPublicWeb3API(stack) + server.RegisterName("web3", web3) + + var ethereum *eth.Ethereum + if err := stack.Service(ðereum); err == nil { + net := eth.NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) + server.RegisterName("net", net) + } else { + glog.V(logger.Warn).Infof("%v\n", err) + } + + buf := &buf{ + requests: make(chan []byte), + responses: make(chan []byte), + } + client := &inProcClient{ + server: server, + buf: buf, + } + + go func() { + server.ServeCodec(rpc.NewJSONCodec(client.buf)) + }() + + return client +} + +// buf represents the connection between the RPC server and console +type buf struct { + readBuf []byte // store remaining request bytes after a partial read + requests chan []byte // list with raw serialized requests + responses chan []byte // list with raw serialized responses +} + +// will read the next request in json format +func (b *buf) Read(p []byte) (int, error) { + // last read didn't read entire request, return remaining bytes + if len(b.readBuf) > 0 { + n := copy(p, b.readBuf) + if n < len(b.readBuf) { + b.readBuf = b.readBuf[:n] + } else { + b.readBuf = b.readBuf[:0] + } + return n, nil + } + + // read next request + req := <-b.requests + n := copy(p, req) + if n < len(req) { + // buf too small, store remaining chunk for next read + b.readBuf = req[n:] + } + + return n, nil +} + +// Write send the given buffer to the backend +func (b *buf) Write(p []byte) (n int, err error) { + b.responses <- p + return len(p), nil +} + +// Close cleans up obtained resources. +func (b *buf) Close() error { + close(b.requests) + close(b.responses) + + return nil +} + +// inProcClient starts a RPC server and uses buf to communicate with it. +type inProcClient struct { + server *rpc.Server + buf *buf +} + +// Close will stop the RPC server +func (c *inProcClient) Close() { + c.server.Stop() +} + +// Send a msg to the endpoint +func (c *inProcClient) Send(msg interface{}) error { + d, err := json.Marshal(msg) + if err != nil { + return err + } + c.buf.requests <- d + return nil +} + +// Recv reads a message and tries to parse it into the given msg +func (c *inProcClient) Recv(msg interface{}) error { + data := <-c.buf.responses + return json.Unmarshal(data, &msg) +} + +// Returns the collection of modules the RPC server offers. +func (c *inProcClient) SupportedModules() (map[string]string, error) { + return rpc.SupportedModules(c) +} + +// NewRemoteRPCClient returns a RPC client which connects to a running geth instance. +// Depending on the given context this can either be a IPC or a HTTP client. +func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) { + if ctx.Args().Present() { + endpoint := ctx.Args().First() + return NewRemoteRPCClientFromString(endpoint) + } + + // use IPC by default + endpoint := IPCSocketPath(ctx) + return rpc.NewIPCClient(endpoint) +} + +// NewRemoteRPCClientFromString returns a RPC client which connects to the given +// endpoint. It must start with either `ipc:` or `rpc:` (HTTP). +func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) { + if strings.HasPrefix(endpoint, "ipc:") { + return rpc.NewIPCClient(endpoint[4:]) + } + if strings.HasPrefix(endpoint, "rpc:") { + return rpc.NewHTTPClient(endpoint[4:]) + } + if strings.HasPrefix(endpoint, "http://") { + return rpc.NewHTTPClient(endpoint) + } + if strings.HasPrefix(endpoint, "ws:") { + return rpc.NewWSClient(endpoint) + } + + return nil, fmt.Errorf("invalid endpoint") +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 63efa08ee..9199432d8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,7 +23,6 @@ import ( "log" "math" "math/big" - "net" "net/http" "os" "path/filepath" @@ -31,6 +30,8 @@ import ( "strconv" "strings" + "errors" + "github.com/codegangsta/cli" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" @@ -49,14 +50,8 @@ import ( "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" - "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/rpc/useragent" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/whisper" - "github.com/ethereum/go-ethereum/xeth" ) func init() { @@ -282,10 +277,10 @@ var ( Usage: "Domains from which to accept cross origin requests (browser enforced)", Value: "", } - RpcApiFlag = cli.StringFlag{ + RPCApiFlag = cli.StringFlag{ Name: "rpcapi", Usage: "API's offered over the HTTP-RPC interface", - Value: comms.DefaultHttpRpcApis, + Value: rpc.DefaultHttpRpcApis, } IPCDisabledFlag = cli.BoolFlag{ Name: "ipcdisable", @@ -294,16 +289,36 @@ var ( IPCApiFlag = cli.StringFlag{ Name: "ipcapi", Usage: "API's offered over the IPC-RPC interface", - Value: comms.DefaultIpcApis, + Value: rpc.DefaultIpcApis, } IPCPathFlag = DirectoryFlag{ Name: "ipcpath", Usage: "Filename for IPC socket/pipe", Value: DirectoryString{common.DefaultIpcPath()}, } - IPCExperimental = cli.BoolFlag{ - Name: "ipcexp", - Usage: "Enable the new RPC implementation", + WSEnabledFlag = cli.BoolFlag{ + Name: "ws", + Usage: "Enable the WS-RPC server", + } + WSListenAddrFlag = cli.StringFlag{ + Name: "wsaddr", + Usage: "WS-RPC server listening interface", + Value: "127.0.0.1", + } + WSPortFlag = cli.IntFlag{ + Name: "wsport", + Usage: "WS-RPC server listening port", + Value: 8546, + } + WSApiFlag = cli.StringFlag{ + Name: "wsapi", + Usage: "API's offered over the WS-RPC interface", + Value: rpc.DefaultHttpRpcApis, + } + WSAllowedDomainsFlag = cli.StringFlag{ + Name: "wsdomains", + Usage: "Domains from which to accept websockets requests", + Value: "", } ExecFlag = cli.StringFlag{ Name: "exec", @@ -760,7 +775,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database return chain, chainDb } -func IpcSocketPath(ctx *cli.Context) (ipcpath string) { +func IPCSocketPath(ctx *cli.Context) (ipcpath string) { if runtime.GOOS == "windows" { ipcpath = common.DefaultIpcPath() if ctx.GlobalIsSet(IPCPathFlag.Name) { @@ -780,79 +795,83 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) { } func StartIPC(stack *node.Node, ctx *cli.Context) error { - config := comms.IpcConfig{ - Endpoint: IpcSocketPath(ctx), - } - var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { return err } - if ctx.GlobalIsSet(IPCExperimental.Name) { - listener, err := comms.CreateListener(config) - if err != nil { - return err - } + endpoint := IPCSocketPath(ctx) + listener, err := rpc.CreateIPCListener(endpoint) + if err != nil { + return err + } - server := rpc.NewServer() + server := rpc.NewServer() - // register package API's this node provides - offered := stack.APIs() - for _, api := range offered { - server.RegisterName(api.Namespace, api.Service) - glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) - } + // register package API's this node provides + offered := stack.APIs() + for _, api := range offered { + server.RegisterName(api.Namespace, api.Service) + glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace) + } - web3 := NewPublicWeb3API(stack) - server.RegisterName("web3", web3) - net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion()) - server.RegisterName("net", net) - - go func() { - glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint) - for { - conn, err := listener.Accept() - if err != nil { - glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) - } - - codec := rpc.NewJSONCodec(conn) - go server.ServeCodec(codec) + go func() { + glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint) + for { + conn, err := listener.Accept() + if err != nil { + glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err) } - }() - - return nil - } - initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { - fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager()) - xeth := xeth.New(stack, fe) - apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack) - if err != nil { - return nil, nil, err + codec := rpc.NewJSONCodec(conn) + go server.ServeCodec(codec) } - return xeth, api.Merge(apis...), nil - } - return comms.StartIpc(config, codec.JSON, initializer) + }() + + return nil + } // StartRPC starts a HTTP JSON-RPC API server. func StartRPC(stack *node.Node, ctx *cli.Context) error { - config := comms.HttpConfig{ - ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name), - ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)), - CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name), + for _, api := range stack.APIs() { + if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok { + address := ctx.GlobalString(RPCListenAddrFlag.Name) + port := ctx.GlobalInt(RPCPortFlag.Name) + cors := ctx.GlobalString(RPCCORSDomainFlag.Name) + apiStr := "" + if ctx.GlobalIsSet(RPCApiFlag.Name) { + apiStr = ctx.GlobalString(RPCApiFlag.Name) + } + + _, err := adminApi.StartRPC(address, port, cors, apiStr) + return err + } } - xeth := xeth.New(stack, nil) - codec := codec.JSON + glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API") + return errors.New("Unable to start RPC-HTTP interface") +} - apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, stack) - if err != nil { - return err +// StartWS starts a websocket JSON-RPC API server. +func StartWS(stack *node.Node, ctx *cli.Context) error { + for _, api := range stack.APIs() { + if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok { + address := ctx.GlobalString(WSListenAddrFlag.Name) + port := ctx.GlobalInt(WSAllowedDomainsFlag.Name) + allowedDomains := ctx.GlobalString(WSAllowedDomainsFlag.Name) + apiStr := "" + if ctx.GlobalIsSet(WSApiFlag.Name) { + apiStr = ctx.GlobalString(WSApiFlag.Name) + } + + _, err := adminApi.StartWS(address, port, allowedDomains, apiStr) + return err + } } - return comms.StartHttp(config, codec, api.Merge(apis...)) + + glog.V(logger.Error).Infof("Unable to start RPC-WS interface, could not find admin API") + return errors.New("Unable to start RPC-WS interface") } func StartPProf(ctx *cli.Context) { diff --git a/cmd/utils/jeth.go b/cmd/utils/jeth.go new file mode 100644 index 000000000..b460597c1 --- /dev/null +++ b/cmd/utils/jeth.go @@ -0,0 +1,323 @@ +// Copyright 2015 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 . + +package utils + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/jsre" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/robertkrimen/otto" +) + +type Jeth struct { + re *jsre.JSRE + client rpc.Client +} + +// NewJeth create a new backend for the JSRE console +func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth { + return &Jeth{re, client} +} + +func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (response otto.Value) { + m := rpc.JSONErrResponse{ + Version: "2.0", + Id: id, + Error: rpc.JSONError{ + Code: code, + Message: msg, + }, + } + + errObj, _ := json.Marshal(m.Error) + errRes, _ := json.Marshal(m) + + call.Otto.Run("ret_error = " + string(errObj)) + res, _ := call.Otto.Run("ret_response = " + string(errRes)) + + return res +} + +// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre +func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) { + var cmd, account, passwd string + timeout := int64(300) + var ok bool + + if len(call.ArgumentList) == 0 { + fmt.Println("expected address of account to unlock") + return otto.FalseValue() + } + + if len(call.ArgumentList) >= 1 { + if accountExport, err := call.Argument(0).Export(); err == nil { + if account, ok = accountExport.(string); ok { + if len(call.ArgumentList) == 1 { + fmt.Printf("Unlock account %s\n", account) + passwd, err = PromptPassword("Passphrase: ", true) + if err != nil { + return otto.FalseValue() + } + } + } + } + } + if len(call.ArgumentList) >= 2 { + if passwdExport, err := call.Argument(1).Export(); err == nil { + passwd, _ = passwdExport.(string) + } + } + + if len(call.ArgumentList) >= 3 { + if timeoutExport, err := call.Argument(2).Export(); err == nil { + timeout, _ = timeoutExport.(int64) + } + } + + cmd = fmt.Sprintf("jeth.unlockAccount('%s', '%s', %d)", account, passwd, timeout) + if val, err := call.Otto.Run(cmd); err == nil { + return val + } + + return otto.FalseValue() +} + +// NewAccount asks the user for the password and than executes the jeth.newAccount callback in the jsre +func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) { + if len(call.ArgumentList) == 0 { + passwd, err := PromptPassword("Passphrase: ", true) + if err != nil { + return otto.FalseValue() + } + passwd2, err := PromptPassword("Repeat passphrase: ", true) + if err != nil { + return otto.FalseValue() + } + + if passwd != passwd2 { + fmt.Println("Passphrases don't match") + return otto.FalseValue() + } + + cmd := fmt.Sprintf("jeth.newAccount('%s')", passwd) + if val, err := call.Otto.Run(cmd); err == nil { + return val + } + } else { + fmt.Println("New account doesn't expect argument(s), you will be prompted for a password") + } + + return otto.FalseValue() +} + +func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { + reqif, err := call.Argument(0).Export() + if err != nil { + return self.err(call, -32700, err.Error(), nil) + } + + jsonreq, err := json.Marshal(reqif) + var reqs []rpc.JSONRequest + batch := true + err = json.Unmarshal(jsonreq, &reqs) + if err != nil { + reqs = make([]rpc.JSONRequest, 1) + err = json.Unmarshal(jsonreq, &reqs[0]) + batch = false + } + + call.Otto.Set("response_len", len(reqs)) + call.Otto.Run("var ret_response = new Array(response_len);") + + for i, req := range reqs { + err := self.client.Send(&req) + if err != nil { + return self.err(call, -32603, err.Error(), req.Id) + } + + result := make(map[string]interface{}) + err = self.client.Recv(&result) + if err != nil { + return self.err(call, -32603, err.Error(), req.Id) + } + + _, isSuccessResponse := result["result"] + _, isErrorResponse := result["error"] + if !isSuccessResponse && !isErrorResponse { + return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64)) + } + + id, _ := result["id"] + call.Otto.Set("ret_id", id) + + jsonver, _ := result["jsonrpc"] + call.Otto.Set("ret_jsonrpc", jsonver) + + var payload []byte + if isSuccessResponse { + payload, _ = json.Marshal(result["result"]) + } else if isErrorResponse { + payload, _ = json.Marshal(result["error"]) + } + call.Otto.Set("ret_result", string(payload)) + call.Otto.Set("response_idx", i) + + response, err = call.Otto.Run(` + ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) }; + `) + } + + if !batch { + call.Otto.Run("ret_response = ret_response[0];") + } + + if call.Argument(1).IsObject() { + call.Otto.Set("callback", call.Argument(1)) + call.Otto.Run(` + if (Object.prototype.toString.call(callback) == '[object Function]') { + callback(null, ret_response); + } + `) + } + + return +} + +/* +// handleRequest will handle user agent requests by interacting with the user and sending +// the user response back to the geth service +func (self *Jeth) handleRequest(req *shared.Request) bool { + var err error + var args []interface{} + if err = json.Unmarshal(req.Params, &args); err != nil { + glog.V(logger.Info).Infof("Unable to parse agent request - %v\n", err) + return false + } + + switch req.Method { + case useragent.AskPasswordMethod: + return self.askPassword(req.Id, req.Jsonrpc, args) + case useragent.ConfirmTransactionMethod: + return self.confirmTransaction(req.Id, req.Jsonrpc, args) + } + + return false +} + +// askPassword will ask the user to supply the password for a given account +func (self *Jeth) askPassword(id interface{}, jsonrpc string, args []interface{}) bool { + var err error + var passwd string + if len(args) >= 1 { + if account, ok := args[0].(string); ok { + fmt.Printf("Unlock account %s\n", account) + } else { + return false + } + } + passwd, err = PromptPassword("Passphrase: ", true) + + if err = self.client.Send(shared.NewRpcResponse(id, jsonrpc, passwd, err)); err != nil { + glog.V(logger.Info).Infof("Unable to send user agent ask password response - %v\n", err) + } + + return err == nil +} + +func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []interface{}) bool { + // Accept all tx which are send from this console + return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil +} +*/ + +// throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error. +func throwJSExeception(msg interface{}) otto.Value { + p, _ := otto.ToValue(msg) + panic(p) + return p +} + +// Sleep will halt the console for arg[0] seconds. +func (self *Jeth) Sleep(call otto.FunctionCall) (response otto.Value) { + if len(call.ArgumentList) >= 1 { + if call.Argument(0).IsNumber() { + sleep, _ := call.Argument(0).ToInteger() + time.Sleep(time.Duration(sleep) * time.Second) + return otto.TrueValue() + } + } + return throwJSExeception("usage: sleep()") +} + +// SleepBlocks will wait for a specified number of new blocks or max for a +// given of seconds. sleepBlocks(nBlocks[, maxSleep]). +func (self *Jeth) SleepBlocks(call otto.FunctionCall) (response otto.Value) { + nBlocks := int64(0) + maxSleep := int64(9999999999999999) // indefinitely + + nArgs := len(call.ArgumentList) + + if nArgs == 0 { + throwJSExeception("usage: sleepBlocks([, max sleep in seconds])") + } + + if nArgs >= 1 { + if call.Argument(0).IsNumber() { + nBlocks, _ = call.Argument(0).ToInteger() + } else { + throwJSExeception("expected number as first argument") + } + } + + if nArgs >= 2 { + if call.Argument(1).IsNumber() { + maxSleep, _ = call.Argument(1).ToInteger() + } else { + throwJSExeception("expected number as second argument") + } + } + + // go through the console, this will allow web3 to call the appropriate + // callbacks if a delayed response or notification is received. + currentBlockNr := func() int64 { + result, err := call.Otto.Run("eth.blockNumber") + if err != nil { + throwJSExeception(err.Error()) + } + blockNr, err := result.ToInteger() + if err != nil { + throwJSExeception(err.Error()) + } + return blockNr + } + + targetBlockNr := currentBlockNr() + nBlocks + deadline := time.Now().Add(time.Duration(maxSleep) * time.Second) + + for time.Now().Before(deadline) { + if currentBlockNr() >= targetBlockNr { + return otto.TrueValue() + } + time.Sleep(time.Second) + } + + return otto.FalseValue() +} -- cgit v1.2.3