diff options
author | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-06-23 22:44:03 +0800 |
---|---|---|
committer | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-06-23 22:44:03 +0800 |
commit | 6b5532ab0d0e88f27af539840c58cbd6de13de90 (patch) | |
tree | 84f1856e1b3fb2fd927e6ef9c59983e4fb347a8c /cmd | |
parent | 139439dcdc14e448bfdbd509bb135faa92337a28 (diff) | |
parent | 2b3957f3737b56622e38425a52caea2a836f8b63 (diff) | |
download | go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.gz go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.bz2 go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.lz go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.xz go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.zst go-tangerine-6b5532ab0d0e88f27af539840c58cbd6de13de90.zip |
Merge pull request #1279 from bas-vk/rpc-http
Integrate console and remove old rpc package structure
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/console/admin.go | 9 | ||||
-rw-r--r-- | cmd/console/contracts.go | 6 | ||||
-rw-r--r-- | cmd/console/js.go | 454 | ||||
-rw-r--r-- | cmd/console/main.go | 103 | ||||
-rw-r--r-- | cmd/geth/admin.go | 937 | ||||
-rw-r--r-- | cmd/geth/js.go | 225 | ||||
-rw-r--r-- | cmd/geth/js_test.go | 19 | ||||
-rw-r--r-- | cmd/geth/main.go | 71 | ||||
-rw-r--r-- | cmd/utils/flags.go | 27 |
9 files changed, 307 insertions, 1544 deletions
diff --git a/cmd/console/admin.go b/cmd/console/admin.go deleted file mode 100644 index dee88e3a0..000000000 --- a/cmd/console/admin.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -/* -node admin bindings -*/ - -func (js *jsre) adminBindings() { - -} diff --git a/cmd/console/contracts.go b/cmd/console/contracts.go deleted file mode 100644 index 1f27838d1..000000000 --- a/cmd/console/contracts.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -var ( - globalRegistrar = `var GlobalRegistrar = web3.eth.contract([{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"name","outputs":[{"name":"o_name","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"content","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"addr","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"subRegistrar","outputs":[{"name":"o_subRegistrar","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_registrar","type":"address"}],"name":"setSubRegistrar","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"Registrar","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_a","type":"address"},{"name":"_primary","type":"bool"}],"name":"setAddress","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_content","type":"bytes32"}],"name":"setContent","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"disown","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"address"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"}],"name":"Changed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"addr","type":"address"}],"name":"PrimaryChanged","type":"event"}]);` - globalRegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b" -) diff --git a/cmd/console/js.go b/cmd/console/js.go deleted file mode 100644 index 15ea9bedd..000000000 --- a/cmd/console/js.go +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This 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 -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -// MA 02110-1301 USA - -package main - -import ( - "bufio" - "fmt" - "math/big" - "os" - "os/signal" - "path/filepath" - "strings" - - "encoding/json" - - "sort" - - "github.com/codegangsta/cli" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common/docserver" - re "github.com/ethereum/go-ethereum/jsre" - "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/peterh/liner" - "github.com/robertkrimen/otto" -) - -type prompter interface { - AppendHistory(string) - Prompt(p string) (string, error) - PasswordPrompt(p string) (string, error) -} - -type dumbterm struct{ r *bufio.Reader } - -func (r dumbterm) Prompt(p string) (string, error) { - fmt.Print(p) - line, err := r.r.ReadString('\n') - return strings.TrimSuffix(line, "\n"), err -} - -func (r dumbterm) PasswordPrompt(p string) (string, error) { - fmt.Println("!! Unsupported terminal, password will echo.") - fmt.Print(p) - input, err := bufio.NewReader(os.Stdin).ReadString('\n') - fmt.Println() - return input, err -} - -func (r dumbterm) AppendHistory(string) {} - -type jsre struct { - re *re.JSRE - wait chan *big.Int - ps1 string - atexit func() - datadir string - prompter -} - -var ( - loadedModulesMethods map[string][]string -) - -func loadAutoCompletion(js *jsre, ipcpath string) { - modules, err := js.suportedApis(ipcpath) - if err != nil { - utils.Fatalf("Unable to determine supported modules - %v", err) - } - - loadedModulesMethods = make(map[string][]string) - for module, _ := range modules { - loadedModulesMethods[module] = api.AutoCompletion[module] - } -} - -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) - } - } 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 { - return "", nil, "" - } - - 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 - } - - begin := line[:i] - keyword := line[i:pos] - end := line[pos:] - - completionWords := keywordCompleter(keyword) - return begin, completionWords, end -} - -func newJSRE(libPath, ipcpath string) *jsre { - js := &jsre{ps1: "> "} - js.wait = make(chan *big.Int) - - // update state in separare forever blocks - js.re = re.New(libPath) - js.apiBindings(ipcpath) - - if !liner.TerminalSupported() { - js.prompter = dumbterm{bufio.NewReader(os.Stdin)} - } else { - lr := liner.NewLiner() - js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) - lr.SetCtrlCAborts(true) - loadAutoCompletion(js, ipcpath) - 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 (js *jsre) apiBindings(ipcpath string) { - ethApi := rpc.NewEthereumApi(nil) - jeth := rpc.NewJeth(ethApi, js.re, ipcpath) - - js.re.Set("jeth", struct{}{}) - t, _ := js.re.Get("jeth") - jethObj := t.Object() - jethObj.Set("send", jeth.SendIpc) - jethObj.Set("sendAsync", jeth.SendIpc) - - err := js.re.Compile("bignumber.js", re.BigNumber_JS) - if err != nil { - utils.Fatalf("Error loading bignumber.js: %v", err) - } - - err = js.re.Compile("ethereum.js", re.Web3_JS) - if err != nil { - utils.Fatalf("Error loading web3.js: %v", err) - } - - _, err = js.re.Eval("var web3 = require('web3');") - if err != nil { - utils.Fatalf("Error requiring web3: %v", err) - } - - _, err = js.re.Eval("web3.setProvider(jeth)") - if err != nil { - utils.Fatalf("Error setting web3 provider: %v", err) - } - - apis, err := js.suportedApis(ipcpath) - if err != nil { - utils.Fatalf("Unable to determine supported api's: %v", err) - } - - // load only supported API's in javascript runtime - shortcuts := "var eth = web3.eth; " - for apiName, _ := range apis { - if apiName == api.Web3ApiName || apiName == api.EthApiName { - continue // manually mapped - } - - 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) - } - } - - _, err = js.re.Eval(shortcuts) - - if err != nil { - utils.Fatalf("Error setting namespaces: %v", err) - } - - js.re.Eval(globalRegistrar + "registrar = GlobalRegistrar.at(\"" + globalRegistrarAddr + "\");") -} - -var ds, _ = docserver.New("/") - -/* -func (self *jsre) ConfirmTransaction(tx string) bool { - if self.ethereum.NatSpec { - notice := natspec.GetNotice(self.xeth, tx, ds) - fmt.Println(notice) - answer, _ := self.Prompt("Confirm Transaction [y/n]") - return strings.HasPrefix(strings.Trim(answer, " "), "y") - } else { - return true - } -} - -func (self *jsre) UnlockAccount(addr []byte) bool { - fmt.Printf("Please unlock account %x.\n", addr) - pass, err := self.PasswordPrompt("Passphrase: ") - if err != nil { - return false - } - // TODO: allow retry - if err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil { - return false - } else { - fmt.Println("Account is now unlocked for this session.") - return true - } -} -*/ - -func (self *jsre) exec(filename string) error { - if err := self.re.Exec(filename); err != nil { - self.re.Stop(false) - return fmt.Errorf("Javascript Error: %v", err) - } - self.re.Stop(true) - return nil -} - -func (self *jsre) suportedApis(ipcpath string) (map[string]string, error) { - config := comms.IpcConfig{ - Endpoint: ipcpath, - } - - client, err := comms.NewIpcClient(config, codec.JSON) - if err != nil { - return nil, err - } - - req := shared.Request{ - Id: 1, - Jsonrpc: "2.0", - Method: "modules", - } - - err = client.Send(req) - if err != nil { - return nil, err - } - - res, err := client.Recv() - if err != nil { - return nil, err - } - - if sucRes, ok := res.(shared.SuccessResponse); ok { - data, _ := json.Marshal(sucRes.Result) - apis := make(map[string]string) - err = json.Unmarshal(data, &apis) - if err == nil { - return apis, nil - } - } - - return nil, fmt.Errorf("Unable to determine supported API's") -} - -// show summary of current geth instance -func (self *jsre) welcome(ipcpath string) { - 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.suportedApis(ipcpath); 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);`) - } -} - -func (self *jsre) batch(args cli.Args) { - statement := strings.Join(args, " ") - val, err := self.re.Run(statement) - - if err != nil { - fmt.Printf("error: %v", err) - } else if val.IsDefined() && val.IsObject() { - obj, _ := self.re.Get("ret_result") - fmt.Printf("%v", obj) - } else if val.IsDefined() { - fmt.Printf("%v", val) - } - - if self.atexit != nil { - self.atexit() - } - - self.re.Stop(false) -} - -func (self *jsre) interactive(ipcpath string) { - self.welcome(ipcpath) - - // Read input lines. - prompt := make(chan string) - inputln := make(chan string) - go func() { - defer close(inputln) - for { - line, err := self.Prompt(<-prompt) - if err != nil { - return - } - inputln <- line - } - }() - // Wait for Ctrl-C, too. - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt) - - defer func() { - if self.atexit != nil { - self.atexit() - } - self.re.Stop(false) - }() - for { - prompt <- self.ps1 - select { - case <-sig: - fmt.Println("caught interrupt, exiting") - return - case input, ok := <-inputln: - if !ok || indentCount <= 0 && input == "exit" { - return - } - if input == "" { - continue - } - str += input + "\n" - self.setIndent() - if indentCount <= 0 { - hist := str[:len(str)-1] - self.AppendHistory(hist) - self.parseInput(str) - str = "" - } - } - } -} - -func (self *jsre) withHistory(op func(*os.File)) { - hist, err := os.OpenFile(filepath.Join(self.datadir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) - if err != nil { - fmt.Printf("unable to open history file: %v\n", err) - return - } - op(hist) - hist.Close() -} - -func (self *jsre) parseInput(code string) { - defer func() { - if r := recover(); r != nil { - fmt.Println("[native] error", r) - } - }() - value, err := self.re.Run(code) - if err != nil { - if ottoErr, ok := err.(*otto.Error); ok { - fmt.Println(ottoErr.String()) - } else { - fmt.Println(err) - } - return - } - self.printValue(value) -} - -var indentCount = 0 -var str = "" - -func (self *jsre) setIndent() { - open := strings.Count(str, "{") - open += strings.Count(str, "(") - closed := strings.Count(str, "}") - closed += strings.Count(str, ")") - indentCount = open - closed - if indentCount <= 0 { - self.ps1 = "> " - } else { - self.ps1 = strings.Join(make([]string, indentCount*2), "..") - self.ps1 += " " - } -} - -func (self *jsre) printValue(v interface{}) { - val, err := self.re.PrettyPrint(v) - if err == nil { - fmt.Printf("%v", val) - } -} diff --git a/cmd/console/main.go b/cmd/console/main.go deleted file mode 100644 index 00a9ca9c4..000000000 --- a/cmd/console/main.go +++ /dev/null @@ -1,103 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @authors - * Jeffrey Wilcke <i@jev.io> - */ -package main - -import ( - "fmt" - "io" - "os" - - "github.com/codegangsta/cli" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/logger" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" -) - -const ( - ClientIdentifier = "Geth console" - Version = "0.9.27" -) - -var ( - gitCommit string // set via linker flag - nodeNameVersion string - app = utils.NewApp(Version, "the geth console") -) - -func init() { - if gitCommit == "" { - nodeNameVersion = Version - } else { - nodeNameVersion = Version + "-" + gitCommit[:8] - } - - app.Action = run - app.Flags = []cli.Flag{ - utils.IPCPathFlag, - utils.VerbosityFlag, - utils.JSpathFlag, - } - - app.Before = func(ctx *cli.Context) error { - utils.SetupLogger(ctx) - return nil - } -} - -func main() { - // 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 interrupted = false - utils.RegisterInterrupt(func(os.Signal) { - interrupted = true - }) - utils.HandleInterrupt() - - if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, "Error: ", err) - } - - // we need to run the interrupt callbacks in case gui is closed - // this skips if we got here by actual interrupt stopping the GUI - if !interrupted { - utils.RunInterruptCallbacks(os.Interrupt) - } - logger.Flush() -} - -func run(ctx *cli.Context) { - jspath := ctx.GlobalString(utils.JSpathFlag.Name) - ipcpath := utils.IpcSocketPath(ctx) - repl := newJSRE(jspath, ipcpath) - - if ctx.Args().Present() { - repl.batch(ctx.Args()) - } else { - repl.interactive(ipcpath) - } -} diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go deleted file mode 100644 index 33ef69792..000000000 --- a/cmd/geth/admin.go +++ /dev/null @@ -1,937 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - "strconv" - "time" - - "github.com/ethereum/ethash" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/common/natspec" - "github.com/ethereum/go-ethereum/common/resolver" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/xeth" - "github.com/robertkrimen/otto" - "gopkg.in/fatih/set.v0" -) - -/* -node admin bindings -*/ - -func (js *jsre) adminBindings() { - ethO, _ := js.re.Get("eth") - eth := ethO.Object() - eth.Set("pendingTransactions", js.pendingTransactions) - eth.Set("resend", js.resend) - eth.Set("sign", js.sign) - - js.re.Set("admin", struct{}{}) - t, _ := js.re.Get("admin") - admin := t.Object() - admin.Set("addPeer", js.addPeer) - admin.Set("startRPC", js.startRPC) - admin.Set("stopRPC", js.stopRPC) - admin.Set("nodeInfo", js.nodeInfo) - admin.Set("peers", js.peers) - admin.Set("newAccount", js.newAccount) - admin.Set("unlock", js.unlock) - admin.Set("import", js.importChain) - admin.Set("export", js.exportChain) - admin.Set("verbosity", js.verbosity) - admin.Set("progress", js.syncProgress) - admin.Set("setSolc", js.setSolc) - - admin.Set("contractInfo", struct{}{}) - t, _ = admin.Get("contractInfo") - cinfo := t.Object() - // newRegistry officially not documented temporary option - cinfo.Set("start", js.startNatSpec) - cinfo.Set("stop", js.stopNatSpec) - cinfo.Set("newRegistry", js.newRegistry) - cinfo.Set("get", js.getContractInfo) - cinfo.Set("register", js.register) - cinfo.Set("registerUrl", js.registerUrl) - // cinfo.Set("verify", js.verify) - - admin.Set("miner", struct{}{}) - t, _ = admin.Get("miner") - miner := t.Object() - miner.Set("start", js.startMining) - miner.Set("stop", js.stopMining) - miner.Set("hashrate", js.hashrate) - miner.Set("setExtra", js.setExtra) - miner.Set("setGasPrice", js.setGasPrice) - miner.Set("startAutoDAG", js.startAutoDAG) - miner.Set("stopAutoDAG", js.stopAutoDAG) - miner.Set("makeDAG", js.makeDAG) - - admin.Set("txPool", struct{}{}) - t, _ = admin.Get("txPool") - txPool := t.Object() - txPool.Set("pending", js.allPendingTransactions) - txPool.Set("queued", js.allQueuedTransactions) - - admin.Set("debug", struct{}{}) - t, _ = admin.Get("debug") - debug := t.Object() - js.re.Set("sleep", js.sleep) - debug.Set("backtrace", js.backtrace) - debug.Set("printBlock", js.printBlock) - debug.Set("dumpBlock", js.dumpBlock) - debug.Set("getBlockRlp", js.getBlockRlp) - debug.Set("setHead", js.setHead) - debug.Set("processBlock", js.debugBlock) - debug.Set("seedhash", js.seedHash) - debug.Set("insertBlock", js.insertBlockRlp) - // undocumented temporary - debug.Set("waitForBlocks", js.waitForBlocks) -} - -// generic helper to getBlock by Number/Height or Hex depending on autodetected input -// if argument is missing the current block is returned -// if block is not found or there is problem with decoding -// the appropriate value is returned and block is guaranteed to be nil -func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) { - var block *types.Block - if len(call.ArgumentList) > 0 { - if call.Argument(0).IsNumber() { - num, _ := call.Argument(0).ToInteger() - block = js.ethereum.ChainManager().GetBlockByNumber(uint64(num)) - } else if call.Argument(0).IsString() { - hash, _ := call.Argument(0).ToString() - block = js.ethereum.ChainManager().GetBlock(common.HexToHash(hash)) - } else { - return nil, errors.New("invalid argument for dump. Either hex string or number") - } - } else { - block = js.ethereum.ChainManager().CurrentBlock() - } - - if block == nil { - return nil, errors.New("block not found") - } - return block, nil -} - -func (js *jsre) seedHash(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) > 0 { - if call.Argument(0).IsNumber() { - num, _ := call.Argument(0).ToInteger() - hash, err := ethash.GetSeedHash(uint64(num)) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash)) - return v - } else { - fmt.Println("arg not a number") - } - } else { - fmt.Println("requires number argument") - } - - return otto.UndefinedValue() -} - -func (js *jsre) allPendingTransactions(call otto.FunctionCall) otto.Value { - txs := js.ethereum.TxPool().GetTransactions() - - ltxs := make([]*tx, len(txs)) - for i, tx := range txs { - // no need to check err - ltxs[i] = newTx(tx) - } - - v, _ := call.Otto.ToValue(ltxs) - return v -} - -func (js *jsre) allQueuedTransactions(call otto.FunctionCall) otto.Value { - txs := js.ethereum.TxPool().GetQueuedTransactions() - - ltxs := make([]*tx, len(txs)) - for i, tx := range txs { - // no need to check err - ltxs[i] = newTx(tx) - } - - v, _ := call.Otto.ToValue(ltxs) - return v -} - -func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value { - txs := js.ethereum.TxPool().GetTransactions() - - // grab the accounts from the account manager. This will help with determening which - // transactions should be returned. - accounts, err := js.ethereum.AccountManager().Accounts() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - // Add the accouns to a new set - accountSet := set.New() - for _, account := range accounts { - accountSet.Add(account.Address) - } - - //ltxs := make([]*tx, len(txs)) - var ltxs []*tx - for _, tx := range txs { - if from, _ := tx.From(); accountSet.Has(from) { - ltxs = append(ltxs, newTx(tx)) - } - } - - v, _ := call.Otto.ToValue(ltxs) - return v -} - -func (js *jsre) resend(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("first argument must be a transaction") - return otto.FalseValue() - } - - v, err := call.Argument(0).Export() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - if tx, ok := v.(*tx); ok { - gl, gp := tx.GasLimit, tx.GasPrice - if len(call.ArgumentList) > 1 { - gp = call.Argument(1).String() - } - if len(call.ArgumentList) > 2 { - gl = call.Argument(2).String() - } - - ret, err := js.xeth.Transact(tx.From, tx.To, tx.Nonce, tx.Value, gl, gp, tx.Data) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx}) - - v, _ := call.Otto.ToValue(ret) - return v - } - - fmt.Println("first argument must be a transaction") - return otto.FalseValue() -} - -func (js *jsre) sign(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 2 { - fmt.Println("requires 2 arguments: eth.sign(signer, data)") - return otto.UndefinedValue() - } - signer, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - data, err := call.Argument(1).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - signed, err := js.xeth.Sign(signer, data, false) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(signed) - return v -} - -func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - tstart := time.Now() - old := vm.Debug - - if len(call.ArgumentList) > 1 { - vm.Debug, _ = call.Argument(1).ToBoolean() - } - - _, err = js.ethereum.BlockProcessor().RetryProcess(block) - if err != nil { - fmt.Println(err) - r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) - return r - } - vm.Debug = old - - r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) - return r -} - -func (js *jsre) insertBlockRlp(call otto.FunctionCall) otto.Value { - tstart := time.Now() - - var block types.Block - if call.Argument(0).IsString() { - blockRlp, _ := call.Argument(0).ToString() - err := rlp.DecodeBytes(common.Hex2Bytes(blockRlp), &block) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - } - - old := vm.Debug - vm.Debug = true - _, err := js.ethereum.BlockProcessor().RetryProcess(&block) - if err != nil { - fmt.Println(err) - r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) - return r - } - vm.Debug = old - - r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) - return r -} - -func (js *jsre) setHead(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - js.ethereum.ChainManager().SetHead(block) - return otto.UndefinedValue() -} - -func (js *jsre) syncProgress(call otto.FunctionCall) otto.Value { - pending, cached, importing, eta := js.ethereum.Downloader().Stats() - v, _ := call.Otto.ToValue(map[string]interface{}{ - "pending": pending, - "cached": cached, - "importing": importing, - "estimate": (eta / time.Second * time.Second).String(), - }) - return v -} - -func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - encoded, _ := rlp.EncodeToBytes(block) - v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded)) - return v -} - -func (js *jsre) setExtra(call otto.FunctionCall) otto.Value { - extra, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - if len(extra) > 1024 { - fmt.Println("error: cannot exceed 1024 bytes") - return otto.UndefinedValue() - } - - js.ethereum.Miner().SetExtra([]byte(extra)) - return otto.UndefinedValue() -} - -func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value { - gasPrice, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - js.ethereum.Miner().SetGasPrice(common.String2Big(gasPrice)) - return otto.UndefinedValue() -} - -func (js *jsre) hashrate(call otto.FunctionCall) otto.Value { - v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate()) - return v -} - -func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value { - blockNumber, err := call.Argument(1).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - err = ethash.MakeDAG(uint64(blockNumber), "") - if err != nil { - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) startAutoDAG(otto.FunctionCall) otto.Value { - js.ethereum.StartAutoDAG() - return otto.TrueValue() -} - -func (js *jsre) stopAutoDAG(otto.FunctionCall) otto.Value { - js.ethereum.StopAutoDAG() - return otto.TrueValue() -} - -func (js *jsre) backtrace(call otto.FunctionCall) otto.Value { - tracestr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - glog.GetTraceLocation().Set(tracestr) - - return otto.UndefinedValue() -} - -func (js *jsre) verbosity(call otto.FunctionCall) otto.Value { - v, err := call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - glog.SetV(int(v)) - return otto.UndefinedValue() -} - -func (js *jsre) startMining(call otto.FunctionCall) otto.Value { - var ( - threads int64 - err error - ) - - if len(call.ArgumentList) > 0 { - threads, err = call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } else { - threads = int64(js.ethereum.MinerThreads) - } - - // switch on DAG autogeneration when miner starts - js.ethereum.StartAutoDAG() - - err = js.ethereum.StartMining(int(threads)) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -func (js *jsre) stopMining(call otto.FunctionCall) otto.Value { - js.ethereum.StopMining() - js.ethereum.StopAutoDAG() - return otto.TrueValue() -} - -func (js *jsre) startRPC(call otto.FunctionCall) otto.Value { - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - port, err := call.Argument(1).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - corsDomain := js.corsDomain - if len(call.ArgumentList) > 2 { - corsDomain, err = call.Argument(2).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } - - config := rpc.RpcConfig{ - ListenAddress: addr, - ListenPort: uint(port), - CorsDomain: corsDomain, - } - - xeth := xeth.New(js.ethereum, nil) - err = rpc.Start(xeth, config) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -func (js *jsre) stopRPC(call otto.FunctionCall) otto.Value { - if rpc.Stop() == nil { - return otto.TrueValue() - } - return otto.FalseValue() -} - -func (js *jsre) addPeer(call otto.FunctionCall) otto.Value { - nodeURL, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - err = js.ethereum.AddPeer(nodeURL) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) unlock(call otto.FunctionCall) otto.Value { - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - seconds, err := call.Argument(2).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if seconds == 0 { - seconds = accounts.DefaultAccountUnlockDuration - } - - arg := call.Argument(1) - var passphrase string - if arg.IsUndefined() { - fmt.Println("Please enter a passphrase now.") - passphrase, err = utils.PromptPassword("Passphrase: ", true) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } else { - passphrase, err = arg.ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } - am := js.ethereum.AccountManager() - err = am.TimedUnlock(common.HexToAddress(addr), passphrase, time.Duration(seconds)*time.Second) - if err != nil { - fmt.Printf("Unlock account failed '%v'\n", err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) newAccount(call otto.FunctionCall) otto.Value { - arg := call.Argument(0) - var passphrase string - if arg.IsUndefined() { - fmt.Println("The new account will be encrypted with a passphrase.") - fmt.Println("Please enter a passphrase now.") - auth, err := utils.PromptPassword("Passphrase: ", true) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - confirm, err := utils.PromptPassword("Repeat Passphrase: ", false) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if auth != confirm { - fmt.Println("Passphrases did not match.") - return otto.FalseValue() - } - passphrase = auth - } else { - var err error - passphrase, err = arg.ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - } - acct, err := js.ethereum.AccountManager().NewAccount(passphrase) - if err != nil { - fmt.Printf("Could not create the account: %v", err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(acct.Address.Hex()) - return v -} - -func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value { - v, _ := call.Otto.ToValue(js.ethereum.NodeInfo()) - return v -} - -func (js *jsre) peers(call otto.FunctionCall) otto.Value { - v, _ := call.Otto.ToValue(js.ethereum.PeersInfo()) - return v -} - -func (js *jsre) importChain(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("require file name. admin.importChain(filename)") - return otto.FalseValue() - } - fn, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if err := utils.ImportChain(js.ethereum.ChainManager(), fn); err != nil { - fmt.Println("Import error: ", err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) exportChain(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) == 0 { - fmt.Println("require file name: admin.exportChain(filename)") - return otto.FalseValue() - } - - fn, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - if err := utils.ExportChain(js.ethereum.ChainManager(), fn); err != nil { - fmt.Println(err) - return otto.FalseValue() - } - return otto.TrueValue() -} - -func (js *jsre) printBlock(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - fmt.Println(block) - - return otto.UndefinedValue() -} - -func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value { - block, err := js.getBlock(call) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - statedb := state.New(block.Root(), js.ethereum.StateDb()) - dump := statedb.RawDump() - v, _ := call.Otto.ToValue(dump) - return v -} - -func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) > 2 { - fmt.Println("requires 0, 1 or 2 arguments: admin.debug.waitForBlock(minHeight, timeout)") - return otto.FalseValue() - } - var n, timeout int64 - var timer <-chan time.Time - var height *big.Int - var err error - args := len(call.ArgumentList) - if args == 2 { - timeout, err = call.Argument(1).ToInteger() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - timer = time.NewTimer(time.Duration(timeout) * time.Second).C - } - if args >= 1 { - n, err = call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - height = big.NewInt(n) - } - - if args == 0 { - height = js.xeth.CurrentBlock().Number() - height.Add(height, common.Big1) - } - - wait := js.wait - js.wait <- height - select { - case <-timer: - // if times out make sure the xeth loop does not block - go func() { - select { - case wait <- nil: - case <-wait: - } - }() - return otto.UndefinedValue() - case height = <-wait: - } - v, _ := call.Otto.ToValue(height.Uint64()) - return v -} - -func (js *jsre) sleep(call otto.FunctionCall) otto.Value { - sec, err := call.Argument(0).ToInteger() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - time.Sleep(time.Duration(sec) * time.Second) - return otto.UndefinedValue() -} - -func (js *jsre) setSolc(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 1 { - fmt.Println("needs 1 argument: admin.contractInfo.setSolc(solcPath)") - return otto.FalseValue() - } - solcPath, err := call.Argument(0).ToString() - if err != nil { - return otto.FalseValue() - } - solc, err := js.xeth.SetSolc(solcPath) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - fmt.Println(solc.Info()) - return otto.TrueValue() -} - -func (js *jsre) register(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 4 { - fmt.Println("requires 4 arguments: admin.contractInfo.register(fromaddress, contractaddress, contract, filename)") - return otto.UndefinedValue() - } - sender, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - address, err := call.Argument(1).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - raw, err := call.Argument(2).Export() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - jsonraw, err := json.Marshal(raw) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - var contract compiler.Contract - err = json.Unmarshal(jsonraw, &contract) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - filename, err := call.Argument(3).ToString() - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - contenthash, err := compiler.ExtractInfo(&contract, filename) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - // sender and contract address are passed as hex strings - codeb := js.xeth.CodeAtBytes(address) - codehash := common.BytesToHash(crypto.Sha3(codeb)) - - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - registry := resolver.New(js.xeth) - - _, err = registry.RegisterContentHash(common.HexToAddress(sender), codehash, contenthash) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - - v, _ := call.Otto.ToValue(contenthash.Hex()) - return v -} - -func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 3 { - fmt.Println("requires 3 arguments: admin.contractInfo.register(fromaddress, contenthash, filename)") - return otto.FalseValue() - } - sender, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - contenthash, err := call.Argument(1).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - url, err := call.Argument(2).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - registry := resolver.New(js.xeth) - - _, err = registry.RegisterUrl(common.HexToAddress(sender), common.HexToHash(contenthash), url) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value { - if len(call.ArgumentList) != 1 { - fmt.Println("requires 1 argument: admin.contractInfo.register(contractaddress)") - return otto.FalseValue() - } - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - infoDoc, err := natspec.FetchDocsForContract(addr, js.xeth, ds) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - var info compiler.ContractInfo - err = json.Unmarshal(infoDoc, &info) - if err != nil { - fmt.Println(err) - return otto.UndefinedValue() - } - v, _ := call.Otto.ToValue(info) - return v -} - -func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value { - js.ethereum.NatSpec = true - return otto.TrueValue() -} - -func (js *jsre) stopNatSpec(call otto.FunctionCall) otto.Value { - js.ethereum.NatSpec = false - return otto.TrueValue() -} - -func (js *jsre) newRegistry(call otto.FunctionCall) otto.Value { - - if len(call.ArgumentList) != 1 { - fmt.Println("requires 1 argument: admin.contractInfo.newRegistry(adminaddress)") - return otto.FalseValue() - } - addr, err := call.Argument(0).ToString() - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - registry := resolver.New(js.xeth) - err = registry.CreateContracts(common.HexToAddress(addr)) - if err != nil { - fmt.Println(err) - return otto.FalseValue() - } - - return otto.TrueValue() -} - -// internal transaction type which will allow us to resend transactions using `eth.resend` -type tx struct { - tx *types.Transaction - - To string - From string - Nonce string - Value string - Data string - GasLimit string - GasPrice string -} - -func newTx(t *types.Transaction) *tx { - from, _ := t.From() - var to string - if t := t.To(); t != nil { - to = t.Hex() - } - - return &tx{ - tx: t, - To: to, - From: from.Hex(), - Value: t.Amount.String(), - Nonce: strconv.Itoa(int(t.Nonce())), - Data: "0x" + common.Bytes2Hex(t.Data()), - GasLimit: t.GasLimit.String(), - GasPrice: t.GasPrice().String(), - } -} diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 7e6e10ca9..01840ebd9 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" @@ -33,9 +35,13 @@ import ( "github.com/ethereum/go-ethereum/eth" re "github.com/ethereum/go-ethereum/jsre" "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/xeth" "github.com/peterh/liner" "github.com/robertkrimen/otto" + "github.com/ethereum/go-ethereum/rpc/shared" ) type prompter interface { @@ -70,10 +76,104 @@ type jsre struct { ps1 string atexit func() corsDomain string + client comms.EthereumClient prompter } -func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, interactive bool, f xeth.Frontend) *jsre { +var ( + loadedModulesMethods map[string][]string +) + +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) + } + } 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 { + return "", nil, "" + } + + 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 + } + + begin := line[:i] + keyword := line[i:pos] + end := line[pos:] + + completionWords := keywordCompleter(keyword) + 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 js.corsDomain = corsDomain @@ -82,10 +182,18 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, intera } js.xeth = xeth.New(ethereum, f) js.wait = js.xeth.UpdateState() + js.client = client + if clt, ok := js.client.(*comms.InProcClient); ok { + if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, ethereum); err == nil { + clt.Initialize(api.Merge(offeredApis...)) + } + } + // update state in separare forever blocks js.re = re.New(libPath) - js.apiBindings(ipcpath, f) - js.adminBindings() + if err := js.apiBindings(f); err != nil { + utils.Fatalf("Unable to connect - %v", err) + } if !liner.TerminalSupported() || !interactive { js.prompter = dumbterm{bufio.NewReader(os.Stdin)} @@ -93,6 +201,9 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, intera 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) }) @@ -103,11 +214,76 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, intera return js } -func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) { - xe := xeth.New(js.ethereum, f) - ethApi := rpc.NewEthereumApi(xe) - jeth := rpc.NewJeth(ethApi, js.re, ipcpath) +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] + } + } +} + +func (self *jsre) batch(statement string) { + val, err := self.re.Run(statement) + + if err != nil { + fmt.Printf("error: %v", err) + } else if val.IsDefined() && val.IsObject() { + obj, _ := self.re.Get("ret_result") + fmt.Printf("%v", obj) + } else if val.IsDefined() { + fmt.Printf("%v", val) + } + + if self.atexit != nil { + self.atexit() + } + + self.re.Stop(false) +} + +// 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);`) + } +} + +func (self *jsre) supportedApis() (map[string]string, error) { + return self.client.SupportedModules() +} + +func (js *jsre) apiBindings(f xeth.Frontend) error { + apis, err := js.supportedApis() + if err != nil { + return err + } + + apiNames := make([]string, 0, len(apis)) + for a, _ := range apis { + apiNames = append(apiNames, a) + } + + apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.ethereum) + if err != nil { + utils.Fatalf("Unable to determine supported api's: %v", err) + } + jeth := rpc.NewJeth(api.Merge(apiImpl...), js.re, js.client) js.re.Set("jeth", struct{}{}) t, _ := js.re.Get("jeth") jethObj := t.Object() @@ -115,14 +291,14 @@ func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) { jethObj.Set("send", jeth.Send) jethObj.Set("sendAsync", jeth.Send) - err := js.re.Compile("bignumber.js", re.BigNumber_JS) + err = js.re.Compile("bignumber.js", re.BigNumber_JS) if err != nil { utils.Fatalf("Error loading bignumber.js: %v", err) } err = js.re.Compile("ethereum.js", re.Web3_JS) if err != nil { - utils.Fatalf("Error loading ethereum.js: %v", err) + utils.Fatalf("Error loading web3.js: %v", err) } _, err = js.re.Eval("var web3 = require('web3');") @@ -134,17 +310,29 @@ func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) { if err != nil { utils.Fatalf("Error setting web3 provider: %v", err) } - _, err = js.re.Eval(` -var eth = web3.eth; -var shh = web3.shh; -var db = web3.db; -var net = web3.net; - `) + + // 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 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) + } + } + + _, err = js.re.Eval(shortcuts) + if err != nil { utils.Fatalf("Error setting namespaces: %v", err) } js.re.Eval(globalRegistrar + "registrar = GlobalRegistrar.at(\"" + globalRegistrarAddr + "\");") + return nil } var ds, _ = docserver.New("/") @@ -234,7 +422,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/js_test.go b/cmd/geth/js_test.go index 20bde01f3..cfbe26bee 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -20,6 +20,8 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/ethereum/go-ethereum/rpc/codec" ) const ( @@ -105,7 +107,8 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) { t.Errorf("Error creating DocServer: %v", err) } tf := &testjethre{ds: ds, stateDb: ethereum.ChainManager().State().Copy()} - repl := newJSRE(ethereum, assetPath, "", "", false, tf) + client := comms.NewInProcClient(codec.JSON) + repl := newJSRE(ethereum, assetPath, "", client, false, tf) tf.jsre = repl return tmp, tf, ethereum } @@ -125,7 +128,7 @@ func TestNodeInfo(t *testing.T) { defer ethereum.Stop() defer os.RemoveAll(tmp) want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}` - checkEvalJSON(t, repl, `admin.nodeInfo()`, want) + checkEvalJSON(t, repl, `admin.nodeInfo`, want) } func TestAccounts(t *testing.T) { @@ -139,7 +142,7 @@ func TestAccounts(t *testing.T) { checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`) - val, err := repl.re.Run(`admin.newAccount("password")`) + val, err := repl.re.Run(`personal.newAccount("password")`) if err != nil { t.Errorf("expected no error, got %v", err) } @@ -161,7 +164,7 @@ func TestBlockChain(t *testing.T) { defer ethereum.Stop() defer os.RemoveAll(tmp) // get current block dump before export/import. - val, err := repl.re.Run("JSON.stringify(admin.debug.dumpBlock())") + val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))") if err != nil { t.Errorf("expected no error, got %v", err) } @@ -178,14 +181,14 @@ func TestBlockChain(t *testing.T) { ethereum.ChainManager().Reset() - checkEvalJSON(t, repl, `admin.export(`+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.import(`+tmpfileq+`)`, `true`) - checkEvalJSON(t, repl, `admin.debug.dumpBlock()`, beforeExport) + checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`) + checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport) } func TestMining(t *testing.T) { @@ -207,7 +210,7 @@ func TestRPC(t *testing.T) { defer ethereum.Stop() defer os.RemoveAll(tmp) - checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004)`, `true`) + checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`) } func TestCheckTestAccountBalance(t *testing.T) { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 89aae43e5..02ec63300 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -38,6 +38,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/comms" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" ) @@ -202,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 (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. `, }, { @@ -239,9 +251,11 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, + utils.RpcApiFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, + utils.ExecFlag, utils.WhisperEnabledFlag, utils.VMDebugFlag, utils.ProtocolVersionFlag, @@ -294,6 +308,44 @@ 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.GlobalString(utils.JSpathFlag.Name), + client, + true, + nil) + + if ctx.GlobalString(utils.ExecFlag.Name) != "" { + repl.batch(ctx.GlobalString(utils.ExecFlag.Name)) + } else { + repl.welcome() + repl.interactive() + } +} + func console(ctx *cli.Context) { // Wrap the standard output with a colorified stream (windows) if isatty.IsTerminal(os.Stdout.Fd()) { @@ -309,16 +361,24 @@ func console(ctx *cli.Context) { utils.Fatalf("%v", err) } + client := comms.NewInProcClient(codec.JSON) + startEth(ctx, ethereum) repl := newJSRE( ethereum, - ctx.String(utils.JSpathFlag.Name), + ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - utils.IpcSocketPath(ctx), + client, true, nil, ) - repl.interactive() + + if ctx.GlobalString(utils.ExecFlag.Name) != "" { + repl.batch(ctx.GlobalString(utils.ExecFlag.Name)) + } else { + repl.welcome() + repl.interactive() + } ethereum.Stop() ethereum.WaitForShutdown() @@ -331,12 +391,13 @@ func execJSFiles(ctx *cli.Context) { utils.Fatalf("%v", err) } + client := comms.NewInProcClient(codec.JSON) startEth(ctx, ethereum) repl := newJSRE( ethereum, - ctx.String(utils.JSpathFlag.Name), + ctx.GlobalString(utils.JSpathFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name), - utils.IpcSocketPath(ctx), + client, false, nil, ) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 696dbd142..15a577a07 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p/nat" - "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" @@ -209,20 +208,29 @@ var ( Usage: "Domain on which to send Access-Control-Allow-Origin header", Value: "", } + RpcApiFlag = cli.StringFlag{ + Name: "rpcapi", + Usage: "Specify the API's which are offered over the HTTP RPC interface", + Value: comms.DefaultHttpRpcApis, + } IPCDisabledFlag = cli.BoolFlag{ Name: "ipcdisable", Usage: "Disable the IPC-RPC server", } IPCApiFlag = cli.StringFlag{ Name: "ipcapi", - Usage: "Specify the API's which are offered over this interface", - Value: api.DefaultIpcApis, + Usage: "Specify the API's which are offered over the IPC interface", + Value: comms.DefaultIpcApis, } IPCPathFlag = DirectoryFlag{ Name: "ipcpath", Usage: "Filename for IPC socket/pipe", Value: DirectoryString{common.DefaultIpcPath()}, } + ExecFlag = cli.StringFlag{ + Name: "exec", + Usage: "Execute javascript statement (only in combination with console/attach)", + } // Network Settings MaxPeersFlag = cli.IntFlag{ Name: "maxpeers", @@ -453,18 +461,25 @@ func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error { return err } - return comms.StartIpc(config, codec, apis...) + return comms.StartIpc(config, codec, api.Merge(apis...)) } func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error { - config := rpc.RpcConfig{ + config := comms.HttpConfig{ ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name), ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)), CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name), } xeth := xeth.New(eth, nil) - return rpc.Start(xeth, config) + codec := codec.JSON + + apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, eth) + if err != nil { + return err + } + + return comms.StartHttp(config, codec, api.Merge(apis...)) } func StartPProf(ctx *cli.Context) { |