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 | |
parent | 139439dcdc14e448bfdbd509bb135faa92337a28 (diff) | |
parent | 2b3957f3737b56622e38425a52caea2a836f8b63 (diff) | |
download | dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.gz dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.bz2 dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.lz dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.xz dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.tar.zst dexon-6b5532ab0d0e88f27af539840c58cbd6de13de90.zip |
Merge pull request #1279 from bas-vk/rpc-http
Integrate console and remove old rpc package structure
53 files changed, 1725 insertions, 5319 deletions
@@ -10,11 +10,6 @@ geth: @echo "Done building." @echo "Run \"$(GOBIN)/geth\" to launch geth." -console: - build/env.sh go install -v $(shell build/ldflags.sh) ./cmd/console - @echo "Done building." - @echo "Run \"$(GOBIN)/console\" to launch the console." - mist: build/env.sh go install -v $(shell build/ldflags.sh) ./cmd/mist @echo "Done building." 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) { diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 5ebe3576b..34a1733d7 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -149,7 +149,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { return ErrInvalidSender } - // Make sure the account exist. Non existant accounts + // Make sure the account exist. Non existent accounts // haven't got funds and well therefor never pass. if !pool.currentState().HasAccount(from) { return ErrNonExistentAccount diff --git a/rpc/api.go b/rpc/api.go deleted file mode 100644 index a344a8294..000000000 --- a/rpc/api.go +++ /dev/null @@ -1,646 +0,0 @@ -package rpc - -import ( - "bytes" - "encoding/json" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/xeth" -) - -type EthereumApi struct { - eth *xeth.XEth -} - -func NewEthereumApi(xeth *xeth.XEth) *EthereumApi { - api := &EthereumApi{ - eth: xeth, - } - - return api -} - -func (api *EthereumApi) xeth() *xeth.XEth { - return api.eth -} - -func (api *EthereumApi) xethAtStateNum(num int64) *xeth.XEth { - return api.xeth().AtStateNum(num) -} - -func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error { - // Spec at https://github.com/ethereum/wiki/wiki/JSON-RPC - glog.V(logger.Debug).Infof("%s %s", req.Method, req.Params) - - switch req.Method { - case "web3_sha3": - args := new(Sha3Args) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = common.ToHex(crypto.Sha3(common.FromHex(args.Data))) - case "web3_clientVersion": - *reply = api.xeth().ClientVersion() - case "net_version": - *reply = api.xeth().NetworkVersion() - case "net_listening": - *reply = api.xeth().IsListening() - case "net_peerCount": - *reply = newHexNum(api.xeth().PeerCount()) - case "eth_protocolVersion": - *reply = api.xeth().EthVersion() - case "eth_coinbase": - *reply = newHexData(api.xeth().Coinbase()) - case "eth_mining": - *reply = api.xeth().IsMining() - case "eth_gasPrice": - v := api.xeth().DefaultGasPrice() - *reply = newHexNum(v.Bytes()) - case "eth_accounts": - *reply = api.xeth().Accounts() - case "eth_blockNumber": - v := api.xeth().CurrentBlock().Number() - *reply = newHexNum(v.Bytes()) - case "eth_getBalance": - args := new(GetBalanceArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - *reply = api.xethAtStateNum(args.BlockNumber).BalanceAt(args.Address) - //v := api.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address).Balance() - //*reply = common.ToHex(v.Bytes()) - case "eth_getStorage", "eth_storageAt": - args := new(GetStorageArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - *reply = api.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address).Storage() - case "eth_getStorageAt": - args := new(GetStorageAtArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - *reply = api.xethAtStateNum(args.BlockNumber).StorageAt(args.Address, args.Key) - case "eth_getTransactionCount": - args := new(GetTxCountArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - count := api.xethAtStateNum(args.BlockNumber).TxCountAt(args.Address) - *reply = newHexNum(big.NewInt(int64(count)).Bytes()) - case "eth_getBlockTransactionCountByHash": - args := new(HashArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := NewBlockRes(api.xeth().EthBlockByHash(args.Hash), false) - if block == nil { - *reply = nil - } else { - *reply = newHexNum(big.NewInt(int64(len(block.Transactions))).Bytes()) - } - case "eth_getBlockTransactionCountByNumber": - args := new(BlockNumArg) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := NewBlockRes(api.xeth().EthBlockByNumber(args.BlockNumber), false) - if block == nil { - *reply = nil - break - } - - *reply = newHexNum(big.NewInt(int64(len(block.Transactions))).Bytes()) - case "eth_getUncleCountByBlockHash": - args := new(HashArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByHash(args.Hash) - br := NewBlockRes(block, false) - if br == nil { - *reply = nil - break - } - - *reply = newHexNum(big.NewInt(int64(len(br.Uncles))).Bytes()) - case "eth_getUncleCountByBlockNumber": - args := new(BlockNumArg) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByNumber(args.BlockNumber) - br := NewBlockRes(block, false) - if br == nil { - *reply = nil - break - } - - *reply = newHexNum(big.NewInt(int64(len(br.Uncles))).Bytes()) - - case "eth_getData", "eth_getCode": - args := new(GetDataArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - v := api.xethAtStateNum(args.BlockNumber).CodeAtBytes(args.Address) - *reply = newHexData(v) - - case "eth_sign": - args := new(NewSigArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - v, err := api.xeth().Sign(args.From, args.Data, false) - if err != nil { - return err - } - *reply = v - - case "eth_sendRawTransaction": - args := new(NewDataArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - v, err := api.xeth().PushTx(args.Data) - if err != nil { - return err - } - *reply = v - - case "eth_sendTransaction", "eth_transact": - args := new(NewTxArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - // nonce may be nil ("guess" mode) - var nonce string - if args.Nonce != nil { - nonce = args.Nonce.String() - } - - var gas string - if args.Gas == nil { - gas = "" - } else { - gas = args.Gas.String() - } - - var gasprice string - if args.GasPrice == nil { - gasprice = "" - } else { - gasprice = args.GasPrice.String() - } - - v, err := api.xeth().Transact(args.From, args.To, nonce, args.Value.String(), gas, gasprice, args.Data) - if err != nil { - return err - } - *reply = v - case "eth_estimateGas": - _, gas, err := api.doCall(req.Params) - if err != nil { - return err - } - - // TODO unwrap the parent method's ToHex call - if len(gas) == 0 { - *reply = newHexNum(0) - } else { - *reply = newHexNum(gas) - } - case "eth_call": - v, _, err := api.doCall(req.Params) - if err != nil { - return err - } - - // TODO unwrap the parent method's ToHex call - if v == "0x0" { - *reply = newHexData([]byte{}) - } else { - *reply = newHexData(common.FromHex(v)) - } - case "eth_flush": - return NewNotImplementedError(req.Method) - case "eth_getBlockByHash": - args := new(GetBlockByHashArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByHash(args.BlockHash) - br := NewBlockRes(block, args.IncludeTxs) - - *reply = br - case "eth_getBlockByNumber": - args := new(GetBlockByNumberArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByNumber(args.BlockNumber) - br := NewBlockRes(block, args.IncludeTxs) - // If request was for "pending", nil nonsensical fields - if args.BlockNumber == -2 { - br.BlockHash = nil - br.BlockNumber = nil - br.Miner = nil - br.Nonce = nil - br.LogsBloom = nil - } - *reply = br - case "eth_getTransactionByHash": - args := new(HashArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - tx, bhash, bnum, txi := api.xeth().EthTransactionByHash(args.Hash) - if tx != nil { - v := NewTransactionRes(tx) - // if the blockhash is 0, assume this is a pending transaction - if bytes.Compare(bhash.Bytes(), bytes.Repeat([]byte{0}, 32)) != 0 { - v.BlockHash = newHexData(bhash) - v.BlockNumber = newHexNum(bnum) - v.TxIndex = newHexNum(txi) - } - *reply = v - } - case "eth_getTransactionByBlockHashAndIndex": - args := new(HashIndexArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByHash(args.Hash) - br := NewBlockRes(block, true) - if br == nil { - *reply = nil - break - } - - if args.Index >= int64(len(br.Transactions)) || args.Index < 0 { - // return NewValidationError("Index", "does not exist") - *reply = nil - } else { - *reply = br.Transactions[args.Index] - } - case "eth_getTransactionByBlockNumberAndIndex": - args := new(BlockNumIndexArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByNumber(args.BlockNumber) - v := NewBlockRes(block, true) - if v == nil { - *reply = nil - break - } - - if args.Index >= int64(len(v.Transactions)) || args.Index < 0 { - // return NewValidationError("Index", "does not exist") - *reply = nil - } else { - *reply = v.Transactions[args.Index] - } - case "eth_getUncleByBlockHashAndIndex": - args := new(HashIndexArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - br := NewBlockRes(api.xeth().EthBlockByHash(args.Hash), false) - if br == nil { - *reply = nil - return nil - } - - if args.Index >= int64(len(br.Uncles)) || args.Index < 0 { - // return NewValidationError("Index", "does not exist") - *reply = nil - } else { - *reply = br.Uncles[args.Index] - } - case "eth_getUncleByBlockNumberAndIndex": - args := new(BlockNumIndexArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - block := api.xeth().EthBlockByNumber(args.BlockNumber) - v := NewBlockRes(block, true) - - if v == nil { - *reply = nil - return nil - } - - if args.Index >= int64(len(v.Uncles)) || args.Index < 0 { - // return NewValidationError("Index", "does not exist") - *reply = nil - } else { - *reply = v.Uncles[args.Index] - } - - case "eth_getCompilers": - var lang string - if solc, _ := api.xeth().Solc(); solc != nil { - lang = "Solidity" - } - c := []string{lang} - *reply = c - - case "eth_compileLLL", "eth_compileSerpent": - return NewNotImplementedError(req.Method) - - case "eth_compileSolidity": - solc, _ := api.xeth().Solc() - if solc == nil { - return NewNotAvailableError(req.Method, "solc (solidity compiler) not found") - } - - args := new(SourceArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - contracts, err := solc.Compile(args.Source) - if err != nil { - return err - } - *reply = contracts - - case "eth_newFilter": - args := new(BlockFilterArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - id := api.xeth().NewLogFilter(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics) - *reply = newHexNum(big.NewInt(int64(id)).Bytes()) - - case "eth_newBlockFilter": - *reply = newHexNum(api.xeth().NewBlockFilter()) - case "eth_newPendingTransactionFilter": - *reply = newHexNum(api.xeth().NewTransactionFilter()) - case "eth_uninstallFilter": - args := new(FilterIdArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = api.xeth().UninstallFilter(args.Id) - case "eth_getFilterChanges": - args := new(FilterIdArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - switch api.xeth().GetFilterType(args.Id) { - case xeth.BlockFilterTy: - *reply = NewHashesRes(api.xeth().BlockFilterChanged(args.Id)) - case xeth.TransactionFilterTy: - *reply = NewHashesRes(api.xeth().TransactionFilterChanged(args.Id)) - case xeth.LogFilterTy: - *reply = NewLogsRes(api.xeth().LogFilterChanged(args.Id)) - default: - *reply = []string{} // reply empty string slice - } - case "eth_getFilterLogs": - args := new(FilterIdArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = NewLogsRes(api.xeth().Logs(args.Id)) - case "eth_getLogs": - args := new(BlockFilterArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = NewLogsRes(api.xeth().AllLogs(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics)) - case "eth_getWork": - api.xeth().SetMining(true, 0) - *reply = api.xeth().RemoteMining().GetWork() - case "eth_submitWork": - args := new(SubmitWorkArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = api.xeth().RemoteMining().SubmitWork(args.Nonce, common.HexToHash(args.Digest), common.HexToHash(args.Header)) - case "db_putString": - args := new(DbArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - if err := args.requirements(); err != nil { - return err - } - - api.xeth().DbPut([]byte(args.Database+args.Key), args.Value) - - *reply = true - case "db_getString": - args := new(DbArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - if err := args.requirements(); err != nil { - return err - } - - res, _ := api.xeth().DbGet([]byte(args.Database + args.Key)) - *reply = string(res) - case "db_putHex": - args := new(DbHexArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - if err := args.requirements(); err != nil { - return err - } - - api.xeth().DbPut([]byte(args.Database+args.Key), args.Value) - *reply = true - case "db_getHex": - args := new(DbHexArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - if err := args.requirements(); err != nil { - return err - } - - res, _ := api.xeth().DbGet([]byte(args.Database + args.Key)) - *reply = newHexData(res) - - case "shh_version": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Retrieves the currently running whisper protocol version - *reply = api.xeth().WhisperVersion() - - case "shh_post": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Injects a new message into the whisper network - args := new(WhisperMessageArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - err := api.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl) - if err != nil { - return err - } - *reply = true - - case "shh_newIdentity": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Creates a new whisper identity to use for sending/receiving messages - *reply = api.xeth().Whisper().NewIdentity() - - case "shh_hasIdentity": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Checks if an identity if owned or not - args := new(WhisperIdentityArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = api.xeth().Whisper().HasIdentity(args.Identity) - - case "shh_newFilter": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Create a new filter to watch and match messages with - args := new(WhisperFilterArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - id := api.xeth().NewWhisperFilter(args.To, args.From, args.Topics) - *reply = newHexNum(big.NewInt(int64(id)).Bytes()) - - case "shh_uninstallFilter": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Remove an existing filter watching messages - args := new(FilterIdArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = api.xeth().UninstallWhisperFilter(args.Id) - - case "shh_getFilterChanges": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Retrieve all the new messages arrived since the last request - args := new(FilterIdArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = api.xeth().WhisperMessagesChanged(args.Id) - - case "shh_getMessages": - // Short circuit if whisper is not running - if api.xeth().Whisper() == nil { - return NewNotAvailableError(req.Method, "whisper offline") - } - // Retrieve all the cached messages matching a specific, existing filter - args := new(FilterIdArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - *reply = api.xeth().WhisperMessages(args.Id) - - case "eth_hashrate": - *reply = newHexNum(api.xeth().HashRate()) - case "ext_disasm": - args := new(SourceArgs) - if err := json.Unmarshal(req.Params, &args); err != nil { - return err - } - - *reply = vm.Disasm(common.FromHex(args.Source)) - - // case "eth_register": - // // Placeholder for actual type - // args := new(HashIndexArgs) - // if err := json.Unmarshal(req.Params, &args); err != nil { - // return err - // } - // *reply = api.xeth().Register(args.Hash) - // case "eth_unregister": - // args := new(HashIndexArgs) - // if err := json.Unmarshal(req.Params, &args); err != nil { - // return err - // } - // *reply = api.xeth().Unregister(args.Hash) - // case "eth_watchTx": - // args := new(HashIndexArgs) - // if err := json.Unmarshal(req.Params, &args); err != nil { - // return err - // } - // *reply = api.xeth().PullWatchTx(args.Hash) - default: - return NewNotImplementedError(req.Method) - } - - // glog.V(logger.Detail).Infof("Reply: %v\n", reply) - return nil -} - -func (api *EthereumApi) doCall(params json.RawMessage) (string, string, error) { - args := new(CallArgs) - if err := json.Unmarshal(params, &args); err != nil { - return "", "", err - } - - var gas string - if args.Gas == nil { - gas = "" - } else { - gas = args.Gas.String() - } - - var gasprice string - if args.GasPrice == nil { - gasprice = "" - } else { - gasprice = args.GasPrice.String() - } - - return api.xethAtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), gas, gasprice, args.Data) -} diff --git a/rpc/api/admin.go b/rpc/api/admin.go index a6b9cf050..b27482cfe 100644 --- a/rpc/api/admin.go +++ b/rpc/api/admin.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" "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" ) @@ -23,8 +24,6 @@ const ( var ( // mapping between methods and handlers AdminMapping = map[string]adminhandler{ - // "admin_startRPC": (*adminApi).StartRPC, - // "admin_stopRPC": (*adminApi).StopRPC, "admin_addPeer": (*adminApi).AddPeer, "admin_peers": (*adminApi).Peers, "admin_nodeInfo": (*adminApi).NodeInfo, @@ -34,6 +33,8 @@ var ( "admin_chainSyncStatus": (*adminApi).ChainSyncStatus, "admin_setSolc": (*adminApi).SetSolc, "admin_datadir": (*adminApi).DataDir, + "admin_startRPC": (*adminApi).StartRPC, + "admin_stopRPC": (*adminApi).StopRPC, } ) @@ -44,25 +45,25 @@ type adminhandler func(*adminApi, *shared.Request) (interface{}, error) type adminApi struct { xeth *xeth.XEth ethereum *eth.Ethereum - methods map[string]adminhandler - codec codec.ApiCoder + codec codec.Codec + coder codec.ApiCoder } // create a new admin api instance -func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, coder codec.Codec) *adminApi { +func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, codec codec.Codec) *adminApi { return &adminApi{ xeth: xeth, ethereum: ethereum, - methods: AdminMapping, - codec: coder.New(nil), + codec: codec, + coder: codec.New(nil), } } // collection with supported methods func (self *adminApi) Methods() []string { - methods := make([]string, len(self.methods)) + methods := make([]string, len(AdminMapping)) i := 0 - for k := range self.methods { + for k := range AdminMapping { methods[i] = k i++ } @@ -71,7 +72,7 @@ func (self *adminApi) Methods() []string { // Execute given request func (self *adminApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { + if callback, ok := AdminMapping[req.Method]; ok { return callback(self, req) } @@ -79,7 +80,7 @@ func (self *adminApi) Execute(req *shared.Request) (interface{}, error) { } func (self *adminApi) Name() string { - return AdminApiName + return shared.AdminApiName } func (self *adminApi) ApiVersion() string { @@ -88,7 +89,7 @@ func (self *adminApi) ApiVersion() string { func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) { args := new(AddPeerArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { + if err := self.coder.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) } @@ -103,33 +104,6 @@ func (self *adminApi) Peers(req *shared.Request) (interface{}, error) { return self.ethereum.PeersInfo(), nil } -func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) { - return false, nil - // Enable when http rpc interface is refactored to prevent import cycles - // args := new(StartRpcArgs) - // if err := self.codec.Decode(req.Params, &args); err != nil { - // return nil, shared.NewDecodeParamError(err.Error()) - // } - // - // cfg := rpc.RpcConfig{ - // ListenAddress: args.Address, - // ListenPort: args.Port, - // } - // - // err := rpc.Start(self.xeth, cfg) - // if err == nil { - // return true, nil - // } - // return false, err -} - -func (self *adminApi) StopRPC(req *shared.Request) (interface{}, error) { - return false, nil - // Enable when http rpc interface is refactored to prevent import cycles - // rpc.Stop() - // return true, nil -} - func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) { return self.ethereum.NodeInfo(), nil } @@ -149,7 +123,7 @@ func hasAllBlocks(chain *core.ChainManager, bs []*types.Block) bool { func (self *adminApi) ImportChain(req *shared.Request) (interface{}, error) { args := new(ImportExportChainArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { + if err := self.coder.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) } @@ -192,7 +166,7 @@ func (self *adminApi) ImportChain(req *shared.Request) (interface{}, error) { func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) { args := new(ImportExportChainArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { + if err := self.coder.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) } @@ -210,7 +184,7 @@ func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) { func (self *adminApi) Verbosity(req *shared.Request) (interface{}, error) { args := new(VerbosityArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { + if err := self.coder.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) } @@ -222,16 +196,16 @@ func (self *adminApi) ChainSyncStatus(req *shared.Request) (interface{}, error) pending, cached, importing, estimate := self.ethereum.Downloader().Stats() return map[string]interface{}{ - "blocksAvailable": pending, + "blocksAvailable": pending, "blocksWaitingForImport": cached, - "importing": importing, - "estimate": estimate.String(), + "importing": importing, + "estimate": estimate.String(), }, nil } func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) { args := new(SetSolcArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { + if err := self.coder.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) } @@ -241,3 +215,32 @@ func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) { } return solc.Info(), nil } + +func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) { + args := new(StartRPCArgs) + if err := self.coder.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + cfg := comms.HttpConfig{ + ListenAddress: args.ListenAddress, + ListenPort: args.ListenPort, + CorsDomain: args.CorsDomain, + } + + apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.ethereum) + if err != nil { + return false, err + } + + err = comms.StartHttp(cfg, self.codec, Merge(apis...)) + if err == nil { + return true, nil + } + return false, err +} + +func (self *adminApi) StopRPC(req *shared.Request) (interface{}, error) { + comms.StopHttp() + return true, nil +} diff --git a/rpc/api/admin_args.go b/rpc/api/admin_args.go index 56bb57e20..5437971ca 100644 --- a/rpc/api/admin_args.go +++ b/rpc/api/admin_args.go @@ -95,3 +95,55 @@ func (args *SetSolcArgs) UnmarshalJSON(b []byte) (err error) { return shared.NewInvalidTypeError("path", "not a string") } + +type StartRPCArgs struct { + ListenAddress string + ListenPort uint + CorsDomain string + Apis string +} + +func (args *StartRPCArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + args.ListenAddress = "127.0.0.1" + args.ListenPort = 8545 + args.Apis = "net,eth,web3" + + if len(obj) >= 1 { + if addr, ok := obj[0].(string); ok { + args.ListenAddress = addr + } else { + return shared.NewInvalidTypeError("listenAddress", "not a string") + } + } + + if len(obj) >= 2 { + if port, ok := obj[1].(float64); ok && port >= 0 && port <= 64*1024 { + args.ListenPort = uint(port) + } else { + return shared.NewInvalidTypeError("listenPort", "not a valid port number") + } + } + + if len(obj) >= 3 { + if corsDomain, ok := obj[2].(string); ok { + args.CorsDomain = corsDomain + } else { + return shared.NewInvalidTypeError("corsDomain", "not a string") + } + } + + if len(obj) >= 4 { + if apis, ok := obj[3].(string); ok { + args.Apis = apis + } else { + return shared.NewInvalidTypeError("apis", "not a string") + } + } + + return nil +} diff --git a/rpc/api/admin_js.go b/rpc/api/admin_js.go index c3e713c67..97642ade7 100644 --- a/rpc/api/admin_js.go +++ b/rpc/api/admin_js.go @@ -39,6 +39,20 @@ web3._extend({ params: 1, inputFormatter: [web3._extend.utils.formatInputString], outputFormatter: web3._extend.formatters.formatOutputString + }), + new web3._extend.Method({ + name: 'startRPC', + call: 'admin_startRPC', + params: 4, + inputFormatter: [web3._extend.utils.formatInputString,web3._extend.utils.formatInputInteger,web3._extend.utils.formatInputString,web3._extend.utils.formatInputString], + outputFormatter: web3._extend.formatters.formatOutputBool + }), + new web3._extend.Method({ + name: 'stopRPC', + call: 'admin_stopRPC', + params: 0, + inputFormatter: [], + outputFormatter: web3._extend.formatters.formatOutputBool }) ], properties: diff --git a/rpc/api/api.go b/rpc/api/api.go index e431e5c1e..ca1ccb9a5 100644 --- a/rpc/api/api.go +++ b/rpc/api/api.go @@ -1,48 +1,10 @@ package api import ( - "strings" - "github.com/ethereum/go-ethereum/rpc/shared" ) -const ( - AdminApiName = "admin" - EthApiName = "eth" - DebugApiName = "debug" - MergedApiName = "merged" - MinerApiName = "miner" - NetApiName = "net" - ShhApiName = "shh" - TxPoolApiName = "txpool" - PersonalApiName = "personal" - Web3ApiName = "web3" -) - -var ( - // List with all API's which are offered over the IPC interface by default - DefaultIpcApis = strings.Join([]string{ - AdminApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, - ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, - }, ",") -) - -// Ethereum RPC API interface -type EthereumApi interface { - // API identifier - Name() string - - // API version - ApiVersion() string - - // Execute the given request and returns the response or an error - Execute(*shared.Request) (interface{}, error) - - // List of supported RCP methods this API provides - Methods() []string -} - // Merge multiple API's to a single API instance -func Merge(apis ...EthereumApi) EthereumApi { +func Merge(apis ...shared.EthereumApi) shared.EthereumApi { return newMergedApi(apis...) } diff --git a/rpc/api/api_test.go b/rpc/api/api_test.go index f1a47944d..7e273ef28 100644 --- a/rpc/api/api_test.go +++ b/rpc/api/api_test.go @@ -3,7 +3,14 @@ package api import ( "testing" + "encoding/json" + "strconv" + + "github.com/ethereum/go-ethereum/common/compiler" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" + "github.com/ethereum/go-ethereum/xeth" ) func TestParseApiString(t *testing.T) { @@ -40,3 +47,107 @@ func TestParseApiString(t *testing.T) { } } + +const solcVersion = "0.9.23" + +func TestCompileSolidity(t *testing.T) { + + solc, err := compiler.New("") + if solc == nil { + t.Skip("no solc found: skip") + } else if solc.Version() != solcVersion { + t.Skip("WARNING: skipping test because of solc different version (%v, test written for %v, may need to update)", solc.Version(), solcVersion) + } + 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` + + jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}` + + expCode := "0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056" + expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]` + expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}` + expDeveloperDoc := `{"methods":{}}` + expCompilerVersion := solc.Version() + expLanguage := "Solidity" + expLanguageVersion := "0" + expSource := source + + xeth := xeth.NewTest(ð.Ethereum{}, nil) + api := NewEthApi(xeth, codec.JSON) + + var rpcRequest shared.Request + json.Unmarshal([]byte(jsonstr), &rpcRequest) + + response, err := api.CompileSolidity(&rpcRequest) + if err != nil { + t.Errorf("Execution failed, %v", err) + } + + respjson, err := json.Marshal(response) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + var contracts = make(map[string]*compiler.Contract) + err = json.Unmarshal(respjson, &contracts) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if len(contracts) != 1 { + t.Errorf("expected one contract, got %v", len(contracts)) + } + + contract := contracts["test"] + + if contract.Code != expCode { + t.Errorf("Expected \n%s got \n%s", expCode, contract.Code) + } + + if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` { + t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source)) + } + + if contract.Info.Language != expLanguage { + t.Errorf("Expected %s got %s", expLanguage, contract.Info.Language) + } + + if contract.Info.LanguageVersion != expLanguageVersion { + t.Errorf("Expected %s got %s", expLanguageVersion, contract.Info.LanguageVersion) + } + + if contract.Info.CompilerVersion != expCompilerVersion { + t.Errorf("Expected %s got %s", expCompilerVersion, contract.Info.CompilerVersion) + } + + userdoc, err := json.Marshal(contract.Info.UserDoc) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + devdoc, err := json.Marshal(contract.Info.DeveloperDoc) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + abidef, err := json.Marshal(contract.Info.AbiDefinition) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + if string(abidef) != expAbiDefinition { + t.Errorf("Expected \n'%s' got \n'%s'", expAbiDefinition, string(abidef)) + } + + if string(userdoc) != expUserDoc { + t.Errorf("Expected \n'%s' got \n'%s'", expUserDoc, string(userdoc)) + } + + if string(devdoc) != expDeveloperDoc { + t.Errorf("Expected %s got %s", expDeveloperDoc, string(devdoc)) + } +} diff --git a/rpc/api/args.go b/rpc/api/args.go new file mode 100644 index 000000000..fc85448e6 --- /dev/null +++ b/rpc/api/args.go @@ -0,0 +1,58 @@ +package api + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/rpc/shared" +) + +type CompileArgs struct { + Source string +} + +func (args *CompileArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + argstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("arg0", "is not a string") + } + args.Source = argstr + + return nil +} + +type FilterStringArgs struct { + Word string +} + +func (args *FilterStringArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + + var argstr string + argstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("filter", "not a string") + } + switch argstr { + case "latest", "pending": + break + default: + return shared.NewValidationError("Word", "Must be `latest` or `pending`") + } + args.Word = argstr + return nil +} diff --git a/rpc/args_test.go b/rpc/api/args_test.go index fd20dbab8..a30f247bc 100644 --- a/rpc/args_test.go +++ b/rpc/api/args_test.go @@ -1,4 +1,4 @@ -package rpc +package api import ( "bytes" @@ -6,6 +6,8 @@ import ( "fmt" "math/big" "testing" + + "github.com/ethereum/go-ethereum/rpc/shared" ) func TestBlockheightInvalidString(t *testing.T) { @@ -68,7 +70,7 @@ func ExpectValidationError(err error) string { switch err.(type) { case nil: str = "Expected error but didn't get one" - case *ValidationError: + case *shared.ValidationError: break default: str = fmt.Sprintf("Expected *rpc.ValidationError but got %T with message `%s`", err, err.Error()) @@ -81,7 +83,7 @@ func ExpectInvalidTypeError(err error) string { switch err.(type) { case nil: str = "Expected error but didn't get one" - case *InvalidTypeError: + case *shared.InvalidTypeError: break default: str = fmt.Sprintf("Expected *rpc.InvalidTypeError but got %T with message `%s`", err, err.Error()) @@ -94,7 +96,7 @@ func ExpectInsufficientParamsError(err error) string { switch err.(type) { case nil: str = "Expected error but didn't get one" - case *InsufficientParamsError: + case *shared.InsufficientParamsError: break default: str = fmt.Sprintf("Expected *rpc.InsufficientParamsError but got %T with message %s", err, err.Error()) @@ -107,7 +109,7 @@ func ExpectDecodeParamError(err error) string { switch err.(type) { case nil: str = "Expected error but didn't get one" - case *DecodeParamError: + case *shared.DecodeParamError: break default: str = fmt.Sprintf("Expected *rpc.DecodeParamError but got %T with message `%s`", err, err.Error()) diff --git a/rpc/api/db.go b/rpc/api/db.go new file mode 100644 index 000000000..6f10d6447 --- /dev/null +++ b/rpc/api/db.go @@ -0,0 +1,128 @@ +package api + +import ( + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" + "github.com/ethereum/go-ethereum/xeth" +) + +const ( + DbApiversion = "1.0" +) + +var ( + // mapping between methods and handlers + DbMapping = map[string]dbhandler{ + "db_getString": (*dbApi).GetString, + "db_putString": (*dbApi).PutString, + "db_getHex": (*dbApi).GetHex, + "db_putHex": (*dbApi).PutHex, + } +) + +// db callback handler +type dbhandler func(*dbApi, *shared.Request) (interface{}, error) + +// db api provider +type dbApi struct { + xeth *xeth.XEth + ethereum *eth.Ethereum + methods map[string]dbhandler + codec codec.ApiCoder +} + +// create a new db api instance +func NewDbApi(xeth *xeth.XEth, ethereum *eth.Ethereum, coder codec.Codec) *dbApi { + return &dbApi{ + xeth: xeth, + ethereum: ethereum, + methods: DbMapping, + codec: coder.New(nil), + } +} + +// collection with supported methods +func (self *dbApi) Methods() []string { + methods := make([]string, len(self.methods)) + i := 0 + for k := range self.methods { + methods[i] = k + i++ + } + return methods +} + +// Execute given request +func (self *dbApi) Execute(req *shared.Request) (interface{}, error) { + if callback, ok := self.methods[req.Method]; ok { + return callback(self, req) + } + + return nil, &shared.NotImplementedError{req.Method} +} + +func (self *dbApi) Name() string { + return shared.DbApiName +} + +func (self *dbApi) ApiVersion() string { + return DbApiversion +} + +func (self *dbApi) GetString(req *shared.Request) (interface{}, error) { + args := new(DbArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + if err := args.requirements(); err != nil { + return nil, err + } + + ret, err := self.xeth.DbGet([]byte(args.Database + args.Key)) + return string(ret), err +} + +func (self *dbApi) PutString(req *shared.Request) (interface{}, error) { + args := new(DbArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + if err := args.requirements(); err != nil { + return nil, err + } + + return self.xeth.DbPut([]byte(args.Database+args.Key), args.Value), nil +} + +func (self *dbApi) GetHex(req *shared.Request) (interface{}, error) { + args := new(DbHexArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + if err := args.requirements(); err != nil { + return nil, err + } + + if res, err := self.xeth.DbGet([]byte(args.Database + args.Key)); err == nil { + return newHexData(res), nil + } else { + return nil, err + } +} + +func (self *dbApi) PutHex(req *shared.Request) (interface{}, error) { + args := new(DbHexArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + if err := args.requirements(); err != nil { + return nil, err + } + + return self.xeth.DbPut([]byte(args.Database+args.Key), args.Value), nil +} diff --git a/rpc/api/db_args.go b/rpc/api/db_args.go new file mode 100644 index 000000000..459616d87 --- /dev/null +++ b/rpc/api/db_args.go @@ -0,0 +1,110 @@ +package api + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc/shared" +) + +type DbArgs struct { + Database string + Key string + Value []byte +} + +func (args *DbArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 2 { + return shared.NewInsufficientParamsError(len(obj), 2) + } + + var objstr string + var ok bool + + if objstr, ok = obj[0].(string); !ok { + return shared.NewInvalidTypeError("database", "not a string") + } + args.Database = objstr + + if objstr, ok = obj[1].(string); !ok { + return shared.NewInvalidTypeError("key", "not a string") + } + args.Key = objstr + + if len(obj) > 2 { + objstr, ok = obj[2].(string) + if !ok { + return shared.NewInvalidTypeError("value", "not a string") + } + + args.Value = []byte(objstr) + } + + return nil +} + +func (a *DbArgs) requirements() error { + if len(a.Database) == 0 { + return shared.NewValidationError("Database", "cannot be blank") + } + if len(a.Key) == 0 { + return shared.NewValidationError("Key", "cannot be blank") + } + return nil +} + +type DbHexArgs struct { + Database string + Key string + Value []byte +} + +func (args *DbHexArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 2 { + return shared.NewInsufficientParamsError(len(obj), 2) + } + + var objstr string + var ok bool + + if objstr, ok = obj[0].(string); !ok { + return shared.NewInvalidTypeError("database", "not a string") + } + args.Database = objstr + + if objstr, ok = obj[1].(string); !ok { + return shared.NewInvalidTypeError("key", "not a string") + } + args.Key = objstr + + if len(obj) > 2 { + objstr, ok = obj[2].(string) + if !ok { + return shared.NewInvalidTypeError("value", "not a string") + } + + args.Value = common.FromHex(objstr) + } + + return nil +} + +func (a *DbHexArgs) requirements() error { + if len(a.Database) == 0 { + return shared.NewValidationError("Database", "cannot be blank") + } + if len(a.Key) == 0 { + return shared.NewValidationError("Key", "cannot be blank") + } + return nil +} diff --git a/rpc/api/db_js.go b/rpc/api/db_js.go new file mode 100644 index 000000000..62cdcd20e --- /dev/null +++ b/rpc/api/db_js.go @@ -0,0 +1,41 @@ +package api + +const Db_JS = ` +web3._extend({ + property: 'db', + methods: + [ + new web3._extend.Method({ + name: 'getString', + call: 'db_getString', + params: 2, + inputFormatter: [web3._extend.formatters.formatInputString, web3._extend.formatters.formatInputString], + outputFormatter: web3._extend.formatters.formatOutputString + }), + new web3._extend.Method({ + name: 'putString', + call: 'db_putString', + params: 3, + inputFormatter: [web3._extend.formatters.formatInputString, web3._extend.formatters.formatInputString, web3._extend.formatters.formatInputString], + outputFormatter: web3._extend.formatters.formatOutputBool + }), + new web3._extend.Method({ + name: 'getHex', + call: 'db_getHex', + params: 2, + inputFormatter: [web3._extend.formatters.formatInputString, web3._extend.formatters.formatInputString], + outputFormatter: web3._extend.formatters.formatOutputString + }), + new web3._extend.Method({ + name: 'putHex', + call: 'db_putHex', + params: 3, + inputFormatter: [web3._extend.formatters.formatInputString, web3._extend.formatters.formatInputString, web3._extend.formatters.formatInputString], + outputFormatter: web3._extend.formatters.formatOutputBool + }), + ], + properties: + [ + ] +}); +` diff --git a/rpc/api/debug.go b/rpc/api/debug.go index 5b6a449dc..b451d8662 100644 --- a/rpc/api/debug.go +++ b/rpc/api/debug.go @@ -71,7 +71,7 @@ func (self *debugApi) Execute(req *shared.Request) (interface{}, error) { } func (self *debugApi) Name() string { - return DebugApiName + return shared.DebugApiName } func (self *debugApi) ApiVersion() string { diff --git a/rpc/api/eth.go b/rpc/api/eth.go index cb678922b..0dff138c6 100644 --- a/rpc/api/eth.go +++ b/rpc/api/eth.go @@ -28,48 +28,49 @@ type ethhandler func(*ethApi, *shared.Request) (interface{}, error) var ( ethMapping = map[string]ethhandler{ - "eth_accounts": (*ethApi).Accounts, - "eth_blockNumber": (*ethApi).BlockNumber, - "eth_getBalance": (*ethApi).GetBalance, - "eth_protocolVersion": (*ethApi).ProtocolVersion, - "eth_coinbase": (*ethApi).Coinbase, - "eth_mining": (*ethApi).IsMining, - "eth_gasPrice": (*ethApi).GasPrice, - "eth_getStorage": (*ethApi).GetStorage, - "eth_storageAt": (*ethApi).GetStorage, - "eth_getStorageAt": (*ethApi).GetStorageAt, - "eth_getTransactionCount": (*ethApi).GetTransactionCount, - "eth_getBlockTransactionCountByHash": (*ethApi).GetBlockTransactionCountByHash, - "eth_getBlockTransactionCountByNumber": (*ethApi).GetBlockTransactionCountByNumber, - "eth_getUncleCountByBlockHash": (*ethApi).GetUncleCountByBlockHash, - "eth_getUncleCountByBlockNumber": (*ethApi).GetUncleCountByBlockNumber, - "eth_getData": (*ethApi).GetData, - "eth_getCode": (*ethApi).GetData, - "eth_sign": (*ethApi).Sign, - "eth_sendRawTransaction": (*ethApi).PushTx, - "eth_sendTransaction": (*ethApi).SendTransaction, - "eth_transact": (*ethApi).SendTransaction, - "eth_estimateGas": (*ethApi).EstimateGas, - "eth_call": (*ethApi).Call, - "eth_flush": (*ethApi).Flush, - "eth_getBlockByHash": (*ethApi).GetBlockByHash, - "eth_getBlockByNumber": (*ethApi).GetBlockByNumber, - "eth_getTransactionByHash": (*ethApi).GetTransactionByHash, - "eth_getTransactionByBlockHashAndIndex": (*ethApi).GetTransactionByBlockHashAndIndex, - "eth_getUncleByBlockHashAndIndex": (*ethApi).GetUncleByBlockHashAndIndex, - "eth_getUncleByBlockNumberAndIndex": (*ethApi).GetUncleByBlockNumberAndIndex, - "eth_getCompilers": (*ethApi).GetCompilers, - "eth_compileSolidity": (*ethApi).CompileSolidity, - "eth_newFilter": (*ethApi).NewFilter, - "eth_newBlockFilter": (*ethApi).NewBlockFilter, - "eth_newPendingTransactionFilter": (*ethApi).NewPendingTransactionFilter, - "eth_uninstallFilter": (*ethApi).UninstallFilter, - "eth_getFilterChanges": (*ethApi).GetFilterChanges, - "eth_getFilterLogs": (*ethApi).GetFilterLogs, - "eth_getLogs": (*ethApi).GetLogs, - "eth_hashrate": (*ethApi).Hashrate, - "eth_getWork": (*ethApi).GetWork, - "eth_submitWork": (*ethApi).SubmitWork, + "eth_accounts": (*ethApi).Accounts, + "eth_blockNumber": (*ethApi).BlockNumber, + "eth_getBalance": (*ethApi).GetBalance, + "eth_protocolVersion": (*ethApi).ProtocolVersion, + "eth_coinbase": (*ethApi).Coinbase, + "eth_mining": (*ethApi).IsMining, + "eth_gasPrice": (*ethApi).GasPrice, + "eth_getStorage": (*ethApi).GetStorage, + "eth_storageAt": (*ethApi).GetStorage, + "eth_getStorageAt": (*ethApi).GetStorageAt, + "eth_getTransactionCount": (*ethApi).GetTransactionCount, + "eth_getBlockTransactionCountByHash": (*ethApi).GetBlockTransactionCountByHash, + "eth_getBlockTransactionCountByNumber": (*ethApi).GetBlockTransactionCountByNumber, + "eth_getUncleCountByBlockHash": (*ethApi).GetUncleCountByBlockHash, + "eth_getUncleCountByBlockNumber": (*ethApi).GetUncleCountByBlockNumber, + "eth_getData": (*ethApi).GetData, + "eth_getCode": (*ethApi).GetData, + "eth_sign": (*ethApi).Sign, + "eth_sendRawTransaction": (*ethApi).SendRawTransaction, + "eth_sendTransaction": (*ethApi).SendTransaction, + "eth_transact": (*ethApi).SendTransaction, + "eth_estimateGas": (*ethApi).EstimateGas, + "eth_call": (*ethApi).Call, + "eth_flush": (*ethApi).Flush, + "eth_getBlockByHash": (*ethApi).GetBlockByHash, + "eth_getBlockByNumber": (*ethApi).GetBlockByNumber, + "eth_getTransactionByHash": (*ethApi).GetTransactionByHash, + "eth_getTransactionByBlockNumberAndIndex": (*ethApi).GetTransactionByBlockNumberAndIndex, + "eth_getTransactionByBlockHashAndIndex": (*ethApi).GetTransactionByBlockHashAndIndex, + "eth_getUncleByBlockHashAndIndex": (*ethApi).GetUncleByBlockHashAndIndex, + "eth_getUncleByBlockNumberAndIndex": (*ethApi).GetUncleByBlockNumberAndIndex, + "eth_getCompilers": (*ethApi).GetCompilers, + "eth_compileSolidity": (*ethApi).CompileSolidity, + "eth_newFilter": (*ethApi).NewFilter, + "eth_newBlockFilter": (*ethApi).NewBlockFilter, + "eth_newPendingTransactionFilter": (*ethApi).NewPendingTransactionFilter, + "eth_uninstallFilter": (*ethApi).UninstallFilter, + "eth_getFilterChanges": (*ethApi).GetFilterChanges, + "eth_getFilterLogs": (*ethApi).GetFilterLogs, + "eth_getLogs": (*ethApi).GetLogs, + "eth_hashrate": (*ethApi).Hashrate, + "eth_getWork": (*ethApi).GetWork, + "eth_submitWork": (*ethApi).SubmitWork, } ) @@ -99,7 +100,7 @@ func (self *ethApi) Execute(req *shared.Request) (interface{}, error) { } func (self *ethApi) Name() string { - return EthApiName + return shared.EthApiName } func (self *ethApi) ApiVersion() string { @@ -115,7 +116,8 @@ func (self *ethApi) Hashrate(req *shared.Request) (interface{}, error) { } func (self *ethApi) BlockNumber(req *shared.Request) (interface{}, error) { - return self.xeth.CurrentBlock().Number(), nil + num := self.xeth.CurrentBlock().Number() + return newHexNum(num.Bytes()), nil } func (self *ethApi) GetBalance(req *shared.Request) (interface{}, error) { @@ -248,8 +250,7 @@ func (self *ethApi) Sign(req *shared.Request) (interface{}, error) { return v, nil } - -func (self *ethApi) PushTx(req *shared.Request) (interface{}, error) { +func (self *ethApi) SendRawTransaction(req *shared.Request) (interface{}, error) { args := new(NewDataArgs) if err := self.codec.Decode(req.Params, &args); err != nil { return nil, shared.NewDecodeParamError(err.Error()) diff --git a/rpc/api/eth_args.go b/rpc/api/eth_args.go index 54eb7201d..bf8ffead6 100644 --- a/rpc/api/eth_args.go +++ b/rpc/api/eth_args.go @@ -227,32 +227,32 @@ func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) { } type NewDataArgs struct { - Data string + Data string } func (args *NewDataArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} + var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } - // Check for sufficient params - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } + // Check for sufficient params + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } - data, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("data", "not a string") - } - args.Data = data + data, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("data", "not a string") + } + args.Data = data - if len(args.Data) == 0 { - return shared.NewValidationError("data", "is required") - } + if len(args.Data) == 0 { + return shared.NewValidationError("data", "is required") + } - return nil + return nil } type NewSigArgs struct { @@ -466,21 +466,21 @@ func (args *CallArgs) UnmarshalJSON(b []byte) (err error) { } args.Value = num - if ext.Gas == nil { - num = big.NewInt(0) - } else { + if ext.Gas != nil { if num, err = numString(ext.Gas); err != nil { return err } + } else { + num = nil } args.Gas = num - if ext.GasPrice == nil { - num = big.NewInt(0) - } else { + if ext.GasPrice != nil { if num, err = numString(ext.GasPrice); err != nil { return err } + } else { + num = nil } args.GasPrice = num diff --git a/rpc/api/eth_js.go b/rpc/api/eth_js.go index f7630bdd5..e1268eb76 100644 --- a/rpc/api/eth_js.go +++ b/rpc/api/eth_js.go @@ -1,3 +1,20 @@ package api // JS api provided by web3.js +// eth_sign not standard + +const Eth_JS = ` +web3._extend({ + property: 'eth', + methods: + [ + new web3._extend.Method({ + name: 'sign', + call: 'eth_sign', + params: 2, + inputFormatter: [web3._extend.formatters.formatInputString,web3._extend.formatters.formatInputString], + outputFormatter: web3._extend.formatters.formatOutputString + }) + ] +}); +` diff --git a/rpc/api/mergedapi.go b/rpc/api/mergedapi.go index b62477a14..bc4fa32e8 100644 --- a/rpc/api/mergedapi.go +++ b/rpc/api/mergedapi.go @@ -1,6 +1,8 @@ package api import ( + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rpc/shared" ) @@ -11,14 +13,14 @@ const ( // combines multiple API's type MergedApi struct { apis map[string]string - methods map[string]EthereumApi + methods map[string]shared.EthereumApi } // create new merged api instance -func newMergedApi(apis ...EthereumApi) *MergedApi { +func newMergedApi(apis ...shared.EthereumApi) *MergedApi { mergedApi := new(MergedApi) mergedApi.apis = make(map[string]string, len(apis)) - mergedApi.methods = make(map[string]EthereumApi) + mergedApi.methods = make(map[string]shared.EthereumApi) for _, api := range apis { mergedApi.apis[api.Name()] = api.ApiVersion() @@ -40,6 +42,8 @@ func (self *MergedApi) Methods() []string { // Call the correct API's Execute method for the given request func (self *MergedApi) Execute(req *shared.Request) (interface{}, error) { + glog.V(logger.Detail).Infof("rpc method: %s", req.Method) + if res, _ := self.handle(req); res != nil { return res, nil } @@ -50,7 +54,7 @@ func (self *MergedApi) Execute(req *shared.Request) (interface{}, error) { } func (self *MergedApi) Name() string { - return MergedApiName + return shared.MergedApiName } func (self *MergedApi) ApiVersion() string { diff --git a/rpc/api/mergedapi_js.go b/rpc/api/mergedapi_js.go deleted file mode 100644 index 778f64ec1..000000000 --- a/rpc/api/mergedapi_js.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/rpc/api/miner.go b/rpc/api/miner.go index 0b5e74f52..7a84cb9ae 100644 --- a/rpc/api/miner.go +++ b/rpc/api/miner.go @@ -66,7 +66,7 @@ func (self *minerApi) Methods() []string { } func (self *minerApi) Name() string { - return MinerApiName + return shared.MinerApiName } func (self *minerApi) ApiVersion() string { diff --git a/rpc/api/net.go b/rpc/api/net.go index d6888ee4a..761654661 100644 --- a/rpc/api/net.go +++ b/rpc/api/net.go @@ -63,7 +63,7 @@ func (self *netApi) Execute(req *shared.Request) (interface{}, error) { } func (self *netApi) Name() string { - return NetApiName + return shared.NetApiName } func (self *netApi) ApiVersion() string { @@ -77,7 +77,7 @@ func (self *netApi) Version(req *shared.Request) (interface{}, error) { // Number of connected peers func (self *netApi) PeerCount(req *shared.Request) (interface{}, error) { - return self.xeth.PeerCount(), nil + return newHexNum(self.xeth.PeerCount()), nil } func (self *netApi) IsListening(req *shared.Request) (interface{}, error) { diff --git a/rpc/api/personal.go b/rpc/api/personal.go index 7a6c91c82..b4a63ea7a 100644 --- a/rpc/api/personal.go +++ b/rpc/api/personal.go @@ -66,7 +66,7 @@ func (self *personalApi) Execute(req *shared.Request) (interface{}, error) { } func (self *personalApi) Name() string { - return PersonalApiName + return shared.PersonalApiName } func (self *personalApi) ApiVersion() string { diff --git a/rpc/api/shh.go b/rpc/api/shh.go index e83a7b22e..18a8fd15d 100644 --- a/rpc/api/shh.go +++ b/rpc/api/shh.go @@ -72,7 +72,7 @@ func (self *shhApi) Execute(req *shared.Request) (interface{}, error) { } func (self *shhApi) Name() string { - return ShhApiName + return shared.ShhApiName } func (self *shhApi) ApiVersion() string { diff --git a/rpc/api/txpool.go b/rpc/api/txpool.go index 64550bdaf..25ad6e9b2 100644 --- a/rpc/api/txpool.go +++ b/rpc/api/txpool.go @@ -60,7 +60,7 @@ func (self *txPoolApi) Execute(req *shared.Request) (interface{}, error) { } func (self *txPoolApi) Name() string { - return TxPoolApiName + return shared.TxPoolApiName } func (self *txPoolApi) ApiVersion() string { diff --git a/rpc/api/utils.go b/rpc/api/utils.go index 40bcae52f..6e4835de6 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/xeth" ) @@ -23,6 +24,14 @@ var ( "chainSyncStatus", "setSolc", "datadir", + "startRPC", + "stopRPC", + }, + "db": []string{ + "getString", + "putString", + "getHex", + "putHex", }, "debug": []string{ "dumpBlock", @@ -123,33 +132,35 @@ var ( ) // Parse a comma separated API string to individual api's -func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.Ethereum) ([]EthereumApi, error) { +func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.Ethereum) ([]shared.EthereumApi, error) { if len(strings.TrimSpace(apistr)) == 0 { return nil, fmt.Errorf("Empty apistr provided") } names := strings.Split(apistr, ",") - apis := make([]EthereumApi, len(names)) + apis := make([]shared.EthereumApi, len(names)) for i, name := range names { switch strings.ToLower(strings.TrimSpace(name)) { - case AdminApiName: + case shared.AdminApiName: apis[i] = NewAdminApi(xeth, eth, codec) - case DebugApiName: + case shared.DebugApiName: apis[i] = NewDebugApi(xeth, eth, codec) - case EthApiName: + case shared.DbApiName: + apis[i] = NewDbApi(xeth, eth, codec) + case shared.EthApiName: apis[i] = NewEthApi(xeth, codec) - case MinerApiName: + case shared.MinerApiName: apis[i] = NewMinerApi(eth, codec) - case NetApiName: + case shared.NetApiName: apis[i] = NewNetApi(xeth, eth, codec) - case ShhApiName: + case shared.ShhApiName: apis[i] = NewShhApi(xeth, eth, codec) - case TxPoolApiName: + case shared.TxPoolApiName: apis[i] = NewTxPoolApi(xeth, eth, codec) - case PersonalApiName: + case shared.PersonalApiName: apis[i] = NewPersonalApi(xeth, eth, codec) - case Web3ApiName: + case shared.Web3ApiName: apis[i] = NewWeb3Api(xeth, codec) default: return nil, fmt.Errorf("Unknown API '%s'", name) @@ -161,19 +172,23 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth. func Javascript(name string) string { switch strings.ToLower(strings.TrimSpace(name)) { - case AdminApiName: + case shared.AdminApiName: return Admin_JS - case DebugApiName: + case shared.DebugApiName: return Debug_JS - case MinerApiName: + case shared.DbApiName: + return Db_JS + case shared.EthApiName: + return Eth_JS + case shared.MinerApiName: return Miner_JS - case NetApiName: + case shared.NetApiName: return Net_JS - case ShhApiName: + case shared.ShhApiName: return Shh_JS - case TxPoolApiName: + case shared.TxPoolApiName: return TxPool_JS - case PersonalApiName: + case shared.PersonalApiName: return Personal_JS } diff --git a/rpc/api/web3.go b/rpc/api/web3.go index ed5008446..4c20baa25 100644 --- a/rpc/api/web3.go +++ b/rpc/api/web3.go @@ -60,7 +60,7 @@ func (self *web3Api) Execute(req *shared.Request) (interface{}, error) { } func (self *web3Api) Name() string { - return Web3ApiName + return shared.Web3ApiName } func (self *web3Api) ApiVersion() string { diff --git a/rpc/api/web3_args.go b/rpc/api/web3_args.go index 5455a6c8e..38af7191e 100644 --- a/rpc/api/web3_args.go +++ b/rpc/api/web3_args.go @@ -1,5 +1,29 @@ package api +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/rpc/shared" +) + type Sha3Args struct { Data string } + +func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + + argstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("data", "is not a string") + } + args.Data = argstr + return nil +} diff --git a/rpc/api_test.go b/rpc/api_test.go deleted file mode 100644 index c3546e98f..000000000 --- a/rpc/api_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package rpc - -import ( - "encoding/json" - "strconv" - "testing" - - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/xeth" -) - -func TestWeb3Sha3(t *testing.T) { - jsonstr := `{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}` - expected := "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" - - api := &EthereumApi{} - - var req RpcRequest - json.Unmarshal([]byte(jsonstr), &req) - - var response interface{} - _ = api.GetRequestReply(&req, &response) - - if response.(string) != expected { - t.Errorf("Expected %s got %s", expected, response) - } -} - -const solcVersion = "0.9.23" - -func TestCompileSolidity(t *testing.T) { - - solc, err := compiler.New("") - if solc == nil { - t.Skip("no solc found: skip") - } else if solc.Version() != solcVersion { - t.Skip("WARNING: skipping test because of solc different version (%v, test written for %v, may need to update)", solc.Version(), solcVersion) - } - 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` - - jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}` - - expCode := "0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056" - expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]` - expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}` - expDeveloperDoc := `{"methods":{}}` - expCompilerVersion := solc.Version() - expLanguage := "Solidity" - expLanguageVersion := "0" - expSource := source - - api := NewEthereumApi(xeth.NewTest(ð.Ethereum{}, nil)) - - var req RpcRequest - json.Unmarshal([]byte(jsonstr), &req) - - var response interface{} - err = api.GetRequestReply(&req, &response) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - respjson, err := json.Marshal(response) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - var contracts = make(map[string]*compiler.Contract) - err = json.Unmarshal(respjson, &contracts) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if len(contracts) != 1 { - t.Errorf("expected one contract, got %v", len(contracts)) - } - - contract := contracts["test"] - - if contract.Code != expCode { - t.Errorf("Expected \n%s got \n%s", expCode, contract.Code) - } - - if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` { - t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source)) - } - - if contract.Info.Language != expLanguage { - t.Errorf("Expected %s got %s", expLanguage, contract.Info.Language) - } - - if contract.Info.LanguageVersion != expLanguageVersion { - t.Errorf("Expected %s got %s", expLanguageVersion, contract.Info.LanguageVersion) - } - - if contract.Info.CompilerVersion != expCompilerVersion { - t.Errorf("Expected %s got %s", expCompilerVersion, contract.Info.CompilerVersion) - } - - userdoc, err := json.Marshal(contract.Info.UserDoc) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - devdoc, err := json.Marshal(contract.Info.DeveloperDoc) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - abidef, err := json.Marshal(contract.Info.AbiDefinition) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if string(abidef) != expAbiDefinition { - t.Errorf("Expected \n'%s' got \n'%s'", expAbiDefinition, string(abidef)) - } - - if string(userdoc) != expUserDoc { - t.Errorf("Expected \n'%s' got \n'%s'", expUserDoc, string(userdoc)) - } - - if string(devdoc) != expDeveloperDoc { - t.Errorf("Expected %s got %s", expDeveloperDoc, string(devdoc)) - } -} - -// func TestDbStr(t *testing.T) { -// jsonput := `{"jsonrpc":"2.0","method":"db_putString","params":["testDB","myKey","myString"],"id":64}` -// jsonget := `{"jsonrpc":"2.0","method":"db_getString","params":["testDB","myKey"],"id":64}` -// expected := "myString" - -// xeth := &xeth.XEth{} -// api := NewEthereumApi(xeth) -// var response interface{} - -// var req RpcRequest -// json.Unmarshal([]byte(jsonput), &req) -// _ = api.GetRequestReply(&req, &response) - -// json.Unmarshal([]byte(jsonget), &req) -// _ = api.GetRequestReply(&req, &response) - -// if response.(string) != expected { -// t.Errorf("Expected %s got %s", expected, response) -// } -// } - -// func TestDbHexStr(t *testing.T) { -// jsonput := `{"jsonrpc":"2.0","method":"db_putHex","params":["testDB","beefKey","0xbeef"],"id":64}` -// jsonget := `{"jsonrpc":"2.0","method":"db_getHex","params":["testDB","beefKey"],"id":64}` -// expected := "0xbeef" - -// xeth := &xeth.XEth{} -// api := NewEthereumApi(xeth) -// defer api.db.Close() -// var response interface{} - -// var req RpcRequest -// json.Unmarshal([]byte(jsonput), &req) -// _ = api.GetRequestReply(&req, &response) - -// json.Unmarshal([]byte(jsonget), &req) -// _ = api.GetRequestReply(&req, &response) - -// if response.(string) != expected { -// t.Errorf("Expected %s got %s", expected, response) -// } -// } - -// func TestFilterClose(t *testing.T) { -// t.Skip() -// api := &EthereumApi{ -// logs: make(map[int]*logFilter), -// messages: make(map[int]*whisperFilter), -// quit: make(chan struct{}), -// } - -// filterTickerTime = 1 -// api.logs[0] = &logFilter{} -// api.messages[0] = &whisperFilter{} -// var wg sync.WaitGroup -// wg.Add(1) -// go api.start() -// go func() { -// select { -// case <-time.After(500 * time.Millisecond): -// api.stop() -// wg.Done() -// } -// }() -// wg.Wait() -// if len(api.logs) != 0 { -// t.Error("expected logs to be empty") -// } - -// if len(api.messages) != 0 { -// t.Error("expected messages to be empty") -// } -// } diff --git a/rpc/args.go b/rpc/args.go deleted file mode 100644 index aa7d20549..000000000 --- a/rpc/args.go +++ /dev/null @@ -1,1230 +0,0 @@ -package rpc - -import ( - "encoding/json" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -const ( - defaultLogLimit = 100 - defaultLogOffset = 0 -) - -func blockHeightFromJson(msg json.RawMessage, number *int64) error { - var raw interface{} - if err := json.Unmarshal(msg, &raw); err != nil { - return NewDecodeParamError(err.Error()) - } - return blockHeight(raw, number) -} - -func blockHeight(raw interface{}, number *int64) error { - // Parse as integer - num, ok := raw.(float64) - if ok { - *number = int64(num) - return nil - } - - // Parse as string/hexstring - str, ok := raw.(string) - if !ok { - return NewInvalidTypeError("", "not a number or string") - } - - switch str { - case "earliest": - *number = 0 - case "latest": - *number = -1 - case "pending": - *number = -2 - default: - if common.HasHexPrefix(str) { - *number = common.String2Big(str).Int64() - } else { - return NewInvalidTypeError("blockNumber", "is not a valid string") - } - } - - return nil -} - -func numString(raw interface{}) (*big.Int, error) { - var number *big.Int - // Parse as integer - num, ok := raw.(float64) - if ok { - number = big.NewInt(int64(num)) - return number, nil - } - - // Parse as string/hexstring - str, ok := raw.(string) - if ok { - number = common.String2Big(str) - return number, nil - } - - return nil, NewInvalidTypeError("", "not a number or string") -} - -// func toNumber(v interface{}) (int64, error) { -// var str string -// if v != nil { -// var ok bool -// str, ok = v.(string) -// if !ok { -// return 0, errors.New("is not a string or undefined") -// } -// } else { -// str = "latest" -// } - -// switch str { -// case "latest": -// return -1, nil -// default: -// return int64(common.Big(v.(string)).Int64()), nil -// } -// } - -// func hashString(raw interface{}, hash *string) error { -// argstr, ok := raw.(string) -// if !ok { -// return NewInvalidTypeError("", "not a string") -// } -// v := common.IsHex(argstr) -// hash = &argstr - -// return nil -// } - -type GetBlockByHashArgs struct { - BlockHash string - IncludeTxs bool -} - -func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - argstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("blockHash", "not a string") - } - args.BlockHash = argstr - - args.IncludeTxs = obj[1].(bool) - - return nil -} - -type GetBlockByNumberArgs struct { - BlockNumber int64 - IncludeTxs bool -} - -func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - args.IncludeTxs = obj[1].(bool) - - return nil -} - -type NewDataArgs struct { - Data string -} - -func (args *NewDataArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - data, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("data", "not a string") - } - args.Data = data - - if len(args.Data) == 0 { - return NewValidationError("data", "is required") - } - - return nil -} - -type NewTxArgs struct { - From string - To string - Nonce *big.Int - Value *big.Int - Gas *big.Int - GasPrice *big.Int - Data string - - BlockNumber int64 -} - -type NewSigArgs struct { - From string - Data string -} - -func (args *NewSigArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - from, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("from", "not a string") - } - args.From = from - - if len(args.From) == 0 { - return NewValidationError("from", "is required") - } - - data, ok := obj[1].(string) - if !ok { - return NewInvalidTypeError("data", "not a string") - } - args.Data = data - - if len(args.Data) == 0 { - return NewValidationError("data", "is required") - } - - return nil -} - -func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { - var obj []json.RawMessage - var ext struct { - From string - To string - Nonce interface{} - Value interface{} - Gas interface{} - GasPrice interface{} - Data string - } - - // Decode byte slice to array of RawMessages - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - // Decode 0th RawMessage to temporary struct - if err := json.Unmarshal(obj[0], &ext); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(ext.From) == 0 { - return NewValidationError("from", "is required") - } - - args.From = ext.From - args.To = ext.To - args.Data = ext.Data - - var num *big.Int - if ext.Nonce != nil { - num, err = numString(ext.Nonce) - if err != nil { - return err - } - } - args.Nonce = num - - if ext.Value == nil { - num = big.NewInt(0) - } else { - num, err = numString(ext.Value) - if err != nil { - return err - } - } - args.Value = num - - num = nil - if ext.Gas != nil { - if num, err = numString(ext.Gas); err != nil { - return err - } - } else { - num = nil - } - args.Gas = num - - num = nil - if ext.GasPrice != nil { - if num, err = numString(ext.GasPrice); err != nil { - return err - } - } else { - num = nil - } - args.GasPrice = num - - // Check for optional BlockNumber param - if len(obj) > 1 { - if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type CallArgs struct { - From string - To string - Value *big.Int - Gas *big.Int - GasPrice *big.Int - Data string - - BlockNumber int64 -} - -func (args *CallArgs) UnmarshalJSON(b []byte) (err error) { - var obj []json.RawMessage - var ext struct { - From string - To string - Value interface{} - Gas interface{} - GasPrice interface{} - Data string - } - - // Decode byte slice to array of RawMessages - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - // Check for sufficient params - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - // Decode 0th RawMessage to temporary struct - if err := json.Unmarshal(obj[0], &ext); err != nil { - return NewDecodeParamError(err.Error()) - } - - args.From = ext.From - - if len(ext.To) == 0 { - return NewValidationError("to", "is required") - } - args.To = ext.To - - var num *big.Int - if ext.Value == nil { - num = big.NewInt(0) - } else { - if num, err = numString(ext.Value); err != nil { - return err - } - } - args.Value = num - - if ext.Gas != nil { - if num, err = numString(ext.Gas); err != nil { - return err - } - } else { - num = nil - } - args.Gas = num - - if ext.GasPrice != nil { - if num, err = numString(ext.GasPrice); err != nil { - return err - } - } else { - num = nil - } - args.GasPrice = num - - args.Data = ext.Data - - // Check for optional BlockNumber param - if len(obj) > 1 { - if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetStorageArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetStorageAtArgs struct { - Address string - Key string - BlockNumber int64 -} - -func (args *GetStorageAtArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - addstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - keystr, ok := obj[1].(string) - if !ok { - return NewInvalidTypeError("key", "not a string") - } - args.Key = keystr - - if len(obj) > 2 { - if err := blockHeight(obj[2], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetTxCountArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetBalanceArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type GetDataArgs struct { - Address string - BlockNumber int64 -} - -func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("address", "not a string") - } - args.Address = addstr - - if len(obj) > 1 { - if err := blockHeight(obj[1], &args.BlockNumber); err != nil { - return err - } - } else { - args.BlockNumber = -1 - } - - return nil -} - -type BlockNumArg struct { - BlockNumber int64 -} - -func (args *BlockNumArg) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - return nil -} - -type BlockNumIndexArgs struct { - BlockNumber int64 - Index int64 -} - -func (args *BlockNumIndexArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - if err := blockHeight(obj[0], &args.BlockNumber); err != nil { - return err - } - - var arg1 *big.Int - if arg1, err = numString(obj[1]); err != nil { - return err - } - args.Index = arg1.Int64() - - return nil -} - -type HashArgs struct { - Hash string -} - -func (args *HashArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - arg0, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("hash", "not a string") - } - args.Hash = arg0 - - return nil -} - -type HashIndexArgs struct { - Hash string - Index int64 -} - -func (args *HashIndexArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - arg0, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("hash", "not a string") - } - args.Hash = arg0 - - arg1, ok := obj[1].(string) - if !ok { - return NewInvalidTypeError("index", "not a string") - } - args.Index = common.Big(arg1).Int64() - - return nil -} - -type Sha3Args struct { - Data string -} - -func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - argstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("data", "is not a string") - } - args.Data = argstr - return nil -} - -type BlockFilterArgs struct { - Earliest int64 - Latest int64 - Address []string - Topics [][]string - Skip int - Max int -} - -func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) { - var obj []struct { - FromBlock interface{} `json:"fromBlock"` - ToBlock interface{} `json:"toBlock"` - Limit interface{} `json:"limit"` - Offset interface{} `json:"offset"` - Address interface{} `json:"address"` - Topics interface{} `json:"topics"` - } - - if err = json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - // args.Earliest, err = toNumber(obj[0].ToBlock) - // if err != nil { - // return NewDecodeParamError(fmt.Sprintf("FromBlock %v", err)) - // } - // args.Latest, err = toNumber(obj[0].FromBlock) - // if err != nil { - // return NewDecodeParamError(fmt.Sprintf("ToBlock %v", err)) - - var num int64 - var numBig *big.Int - - // if blank then latest - if obj[0].FromBlock == nil { - num = -1 - } else { - if err := blockHeight(obj[0].FromBlock, &num); err != nil { - return err - } - } - // if -2 or other "silly" number, use latest - if num < 0 { - args.Earliest = -1 //latest block - } else { - args.Earliest = num - } - - // if blank than latest - if obj[0].ToBlock == nil { - num = -1 - } else { - if err := blockHeight(obj[0].ToBlock, &num); err != nil { - return err - } - } - args.Latest = num - - if obj[0].Limit == nil { - numBig = big.NewInt(defaultLogLimit) - } else { - if numBig, err = numString(obj[0].Limit); err != nil { - return err - } - } - args.Max = int(numBig.Int64()) - - if obj[0].Offset == nil { - numBig = big.NewInt(defaultLogOffset) - } else { - if numBig, err = numString(obj[0].Offset); err != nil { - return err - } - } - args.Skip = int(numBig.Int64()) - - if obj[0].Address != nil { - marg, ok := obj[0].Address.([]interface{}) - if ok { - v := make([]string, len(marg)) - for i, arg := range marg { - argstr, ok := arg.(string) - if !ok { - return NewInvalidTypeError(fmt.Sprintf("address[%d]", i), "is not a string") - } - v[i] = argstr - } - args.Address = v - } else { - argstr, ok := obj[0].Address.(string) - if ok { - v := make([]string, 1) - v[0] = argstr - args.Address = v - } else { - return NewInvalidTypeError("address", "is not a string or array") - } - } - } - - if obj[0].Topics != nil { - other, ok := obj[0].Topics.([]interface{}) - if ok { - topicdbl := make([][]string, len(other)) - for i, iv := range other { - if argstr, ok := iv.(string); ok { - // Found a string, push into first element of array - topicsgl := make([]string, 1) - topicsgl[0] = argstr - topicdbl[i] = topicsgl - } else if argarray, ok := iv.([]interface{}); ok { - // Found an array of other - topicdbl[i] = make([]string, len(argarray)) - for j, jv := range argarray { - if v, ok := jv.(string); ok { - topicdbl[i][j] = v - } else if jv == nil { - topicdbl[i][j] = "" - } else { - return NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", i, j), "is not a string") - } - } - } else if iv == nil { - topicdbl[i] = []string{""} - } else { - return NewInvalidTypeError(fmt.Sprintf("topic[%d]", i), "not a string or array") - } - } - args.Topics = topicdbl - return nil - } else { - return NewInvalidTypeError("topic", "is not a string or array") - } - } - - return nil -} - -type DbArgs struct { - Database string - Key string - Value []byte -} - -func (args *DbArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - var objstr string - var ok bool - - if objstr, ok = obj[0].(string); !ok { - return NewInvalidTypeError("database", "not a string") - } - args.Database = objstr - - if objstr, ok = obj[1].(string); !ok { - return NewInvalidTypeError("key", "not a string") - } - args.Key = objstr - - if len(obj) > 2 { - objstr, ok = obj[2].(string) - if !ok { - return NewInvalidTypeError("value", "not a string") - } - - args.Value = []byte(objstr) - } - - return nil -} - -func (a *DbArgs) requirements() error { - if len(a.Database) == 0 { - return NewValidationError("Database", "cannot be blank") - } - if len(a.Key) == 0 { - return NewValidationError("Key", "cannot be blank") - } - return nil -} - -type DbHexArgs struct { - Database string - Key string - Value []byte -} - -func (args *DbHexArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return NewInsufficientParamsError(len(obj), 2) - } - - var objstr string - var ok bool - - if objstr, ok = obj[0].(string); !ok { - return NewInvalidTypeError("database", "not a string") - } - args.Database = objstr - - if objstr, ok = obj[1].(string); !ok { - return NewInvalidTypeError("key", "not a string") - } - args.Key = objstr - - if len(obj) > 2 { - objstr, ok = obj[2].(string) - if !ok { - return NewInvalidTypeError("value", "not a string") - } - - args.Value = common.FromHex(objstr) - } - - return nil -} - -func (a *DbHexArgs) requirements() error { - if len(a.Database) == 0 { - return NewValidationError("Database", "cannot be blank") - } - if len(a.Key) == 0 { - return NewValidationError("Key", "cannot be blank") - } - return nil -} - -type WhisperMessageArgs struct { - Payload string - To string - From string - Topics []string - Priority uint32 - Ttl uint32 -} - -func (args *WhisperMessageArgs) UnmarshalJSON(b []byte) (err error) { - var obj []struct { - Payload string - To string - From string - Topics []string - Priority interface{} - Ttl interface{} - } - - if err = json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - args.Payload = obj[0].Payload - args.To = obj[0].To - args.From = obj[0].From - args.Topics = obj[0].Topics - - var num *big.Int - if num, err = numString(obj[0].Priority); err != nil { - return err - } - args.Priority = uint32(num.Int64()) - - if num, err = numString(obj[0].Ttl); err != nil { - return err - } - args.Ttl = uint32(num.Int64()) - - return nil -} - -type CompileArgs struct { - Source string -} - -func (args *CompileArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - argstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("arg0", "is not a string") - } - args.Source = argstr - - return nil -} - -type FilterStringArgs struct { - Word string -} - -func (args *FilterStringArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - var argstr string - argstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("filter", "not a string") - } - switch argstr { - case "latest", "pending": - break - default: - return NewValidationError("Word", "Must be `latest` or `pending`") - } - args.Word = argstr - return nil -} - -type FilterIdArgs struct { - Id int -} - -func (args *FilterIdArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - var num *big.Int - if num, err = numString(obj[0]); err != nil { - return err - } - args.Id = int(num.Int64()) - - return nil -} - -type WhisperIdentityArgs struct { - Identity string -} - -func (args *WhisperIdentityArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - argstr, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("arg0", "not a string") - } - // if !common.IsHex(argstr) { - // return NewValidationError("arg0", "not a hexstring") - // } - args.Identity = argstr - - return nil -} - -type WhisperFilterArgs struct { - To string - From string - Topics [][]string -} - -// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a -// JSON message blob into a WhisperFilterArgs structure. -func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { - // Unmarshal the JSON message and sanity check - var obj []struct { - To interface{} `json:"to"` - From interface{} `json:"from"` - Topics interface{} `json:"topics"` - } - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - // Retrieve the simple data contents of the filter arguments - if obj[0].To == nil { - args.To = "" - } else { - argstr, ok := obj[0].To.(string) - if !ok { - return NewInvalidTypeError("to", "is not a string") - } - args.To = argstr - } - if obj[0].From == nil { - args.From = "" - } else { - argstr, ok := obj[0].From.(string) - if !ok { - return NewInvalidTypeError("from", "is not a string") - } - args.From = argstr - } - // Construct the nested topic array - if obj[0].Topics != nil { - // Make sure we have an actual topic array - list, ok := obj[0].Topics.([]interface{}) - if !ok { - return NewInvalidTypeError("topics", "is not an array") - } - // Iterate over each topic and handle nil, string or array - topics := make([][]string, len(list)) - for idx, field := range list { - switch value := field.(type) { - case nil: - topics[idx] = []string{} - - case string: - topics[idx] = []string{value} - - case []interface{}: - topics[idx] = make([]string, len(value)) - for i, nested := range value { - switch value := nested.(type) { - case nil: - topics[idx][i] = "" - - case string: - topics[idx][i] = value - - default: - return NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", idx, i), "is not a string") - } - } - default: - return NewInvalidTypeError(fmt.Sprintf("topic[%d]", idx), "not a string or array") - } - } - args.Topics = topics - } - return nil -} - -type SubmitWorkArgs struct { - Nonce uint64 - Header string - Digest string -} - -func (args *SubmitWorkArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err = json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 3 { - return NewInsufficientParamsError(len(obj), 3) - } - - var objstr string - var ok bool - if objstr, ok = obj[0].(string); !ok { - return NewInvalidTypeError("nonce", "not a string") - } - - args.Nonce = common.String2Big(objstr).Uint64() - if objstr, ok = obj[1].(string); !ok { - return NewInvalidTypeError("header", "not a string") - } - - args.Header = objstr - - if objstr, ok = obj[2].(string); !ok { - return NewInvalidTypeError("digest", "not a string") - } - - args.Digest = objstr - - return nil -} - -type SourceArgs struct { - Source string -} - -func (args *SourceArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return NewInsufficientParamsError(len(obj), 1) - } - - arg0, ok := obj[0].(string) - if !ok { - return NewInvalidTypeError("source code", "not a string") - } - args.Source = arg0 - - return nil -} diff --git a/rpc/comms/comms.go b/rpc/comms/comms.go index 244f5a7a6..bfe625758 100644 --- a/rpc/comms/comms.go +++ b/rpc/comms/comms.go @@ -1,7 +1,111 @@ package comms +import ( + "io" + "net" + + "fmt" + "strings" + + "strconv" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" +) + +const ( + maxHttpSizeReqLength = 1024 * 1024 // 1MB +) + +var ( + // List with all API's which are offered over the in proc interface by default + DefaultInProcApis = shared.AllApis + + // List with all API's which are offered over the IPC interface by default + DefaultIpcApis = shared.AllApis + + // List with API's which are offered over thr HTTP/RPC interface by default + DefaultHttpRpcApis = strings.Join([]string{ + shared.DbApiName, shared.EthApiName, shared.NetApiName, shared.Web3ApiName, + }, ",") +) + type EthereumClient interface { + // Close underlaying connection Close() + // Send request Send(interface{}) error + // Receive response Recv() (interface{}, error) + // List with modules this client supports + SupportedModules() (map[string]string, error) +} + +func handle(conn net.Conn, api shared.EthereumApi, c codec.Codec) { + codec := c.New(conn) + + for { + req, err := codec.ReadRequest() + if err == io.EOF { + codec.Close() + return + } else if err != nil { + glog.V(logger.Error).Infof("comms recv err - %v\n", err) + codec.Close() + return + } + + var rpcResponse interface{} + res, err := api.Execute(req) + + rpcResponse = shared.NewRpcResponse(req.Id, req.Jsonrpc, res, err) + err = codec.WriteResponse(rpcResponse) + if err != nil { + glog.V(logger.Error).Infof("comms send err - %v\n", err) + codec.Close() + return + } + } +} + +// Endpoint must be in the form of: +// ${protocol}:${path} +// e.g. ipc:/tmp/geth.ipc +// rpc:localhost:8545 +func ClientFromEndpoint(endpoint string, c codec.Codec) (EthereumClient, error) { + if strings.HasPrefix(endpoint, "ipc:") { + cfg := IpcConfig{ + Endpoint: endpoint[4:], + } + return NewIpcClient(cfg, codec.JSON) + } + + if strings.HasPrefix(endpoint, "rpc:") { + parts := strings.Split(endpoint, ":") + addr := "http://localhost" + port := uint(8545) + if len(parts) >= 3 { + addr = parts[1] + ":" + parts[2] + } + + if len(parts) >= 4 { + p, err := strconv.Atoi(parts[3]) + + if err != nil { + return nil, err + } + port = uint(p) + } + + cfg := HttpConfig{ + ListenAddress: addr, + ListenPort: port, + } + + return NewHttpClient(cfg, codec.JSON), nil + } + + return nil, fmt.Errorf("Invalid endpoint") } diff --git a/rpc/comms/http.go b/rpc/comms/http.go new file mode 100644 index 000000000..ebee791bd --- /dev/null +++ b/rpc/comms/http.go @@ -0,0 +1,190 @@ +package comms + +import ( + "fmt" + "net/http" + "strings" + + "bytes" + "io/ioutil" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" + "github.com/rs/cors" +) + +var ( + // main HTTP rpc listener + httpListener *stoppableTCPListener + listenerStoppedError = fmt.Errorf("Listener has stopped") +) + +type HttpConfig struct { + ListenAddress string + ListenPort uint + CorsDomain string +} + +func StartHttp(cfg HttpConfig, codec codec.Codec, api shared.EthereumApi) error { + if httpListener != nil { + if fmt.Sprintf("%s:%d", cfg.ListenAddress, cfg.ListenPort) != httpListener.Addr().String() { + return fmt.Errorf("RPC service already running on %s ", httpListener.Addr().String()) + } + return nil // RPC service already running on given host/port + } + + l, err := newStoppableTCPListener(fmt.Sprintf("%s:%d", cfg.ListenAddress, cfg.ListenPort)) + if err != nil { + glog.V(logger.Error).Infof("Can't listen on %s:%d: %v", cfg.ListenAddress, cfg.ListenPort, err) + return err + } + httpListener = l + + var handler http.Handler + if len(cfg.CorsDomain) > 0 { + var opts cors.Options + opts.AllowedMethods = []string{"POST"} + opts.AllowedOrigins = strings.Split(cfg.CorsDomain, " ") + + c := cors.New(opts) + handler = newStoppableHandler(c.Handler(gethHttpHandler(codec, api)), l.stop) + } else { + handler = newStoppableHandler(gethHttpHandler(codec, api), l.stop) + } + + go http.Serve(l, handler) + + return nil +} + +func StopHttp() { + if httpListener != nil { + httpListener.Stop() + httpListener = nil + } +} + +type httpClient struct { + address string + port uint + codec codec.ApiCoder + lastRes interface{} + lastErr error +} + +// Create a new in process client +func NewHttpClient(cfg HttpConfig, c codec.Codec) *httpClient { + return &httpClient{ + address: cfg.ListenAddress, + port: cfg.ListenPort, + codec: c.New(nil), + } +} + +func (self *httpClient) Close() { + // do nothing +} + +func (self *httpClient) Send(req interface{}) error { + var body []byte + var err error + + self.lastRes = nil + self.lastErr = nil + + if body, err = self.codec.Encode(req); err != nil { + return err + } + + httpReq, err := http.NewRequest("POST", fmt.Sprintf("%s:%d", self.address, self.port), bytes.NewBuffer(body)) + if err != nil { + return err + } + httpReq.Header.Set("Content-Type", "application/json") + + client := http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.Status == "200 OK" { + reply, _ := ioutil.ReadAll(resp.Body) + var rpcSuccessResponse shared.SuccessResponse + if err = self.codec.Decode(reply, &rpcSuccessResponse); err == nil { + self.lastRes = rpcSuccessResponse.Result + self.lastErr = err + return nil + } else { + var rpcErrorResponse shared.ErrorResponse + if err = self.codec.Decode(reply, &rpcErrorResponse); err == nil { + self.lastRes = rpcErrorResponse.Error + self.lastErr = err + return nil + } else { + return err + } + } + } + + return fmt.Errorf("Not implemented") +} + +func (self *httpClient) Recv() (interface{}, error) { + return self.lastRes, self.lastErr +} + +func (self *httpClient) SupportedModules() (map[string]string, error) { + var body []byte + var err error + + payload := shared.Request{ + Id: 1, + Jsonrpc: "2.0", + Method: "modules", + } + + if body, err = self.codec.Encode(payload); err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s:%d", self.address, self.port), bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.Status == "200 OK" { + reply, _ := ioutil.ReadAll(resp.Body) + var rpcRes shared.SuccessResponse + if err = self.codec.Decode(reply, &rpcRes); err != nil { + return nil, err + } + + result := make(map[string]string) + if modules, ok := rpcRes.Result.(map[string]interface{}); ok { + for a, v := range modules { + result[a] = fmt.Sprintf("%s", v) + } + return result, nil + } + err = fmt.Errorf("Unable to parse module response - %v", rpcRes.Result) + } else { + fmt.Printf("resp.Status = %s\n", resp.Status) + fmt.Printf("err = %v\n", err) + } + + return nil, err +} diff --git a/rpc/comms/http_net.go b/rpc/comms/http_net.go new file mode 100644 index 000000000..acc5f99a9 --- /dev/null +++ b/rpc/comms/http_net.go @@ -0,0 +1,166 @@ +package comms + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" +) + +// When https://github.com/golang/go/issues/4674 is implemented this could be replaced +type stoppableTCPListener struct { + *net.TCPListener + stop chan struct{} // closed when the listener must stop +} + +func newStoppableTCPListener(addr string) (*stoppableTCPListener, error) { + wl, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + if tcpl, ok := wl.(*net.TCPListener); ok { + stop := make(chan struct{}) + return &stoppableTCPListener{tcpl, stop}, nil + } + + return nil, fmt.Errorf("Unable to create TCP listener for RPC service") +} + +// Stop the listener and all accepted and still active connections. +func (self *stoppableTCPListener) Stop() { + close(self.stop) +} + +func (self *stoppableTCPListener) Accept() (net.Conn, error) { + for { + self.SetDeadline(time.Now().Add(time.Duration(1 * time.Second))) + c, err := self.TCPListener.AcceptTCP() + + select { + case <-self.stop: + if c != nil { // accept timeout + c.Close() + } + self.TCPListener.Close() + return nil, listenerStoppedError + default: + } + + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() && netErr.Temporary() { + continue // regular timeout + } + } + + return &closableConnection{c, self.stop}, err + } +} + +type closableConnection struct { + *net.TCPConn + closed chan struct{} +} + +func (self *closableConnection) Read(b []byte) (n int, err error) { + select { + case <-self.closed: + self.TCPConn.Close() + return 0, io.EOF + default: + return self.TCPConn.Read(b) + } +} + +// Wraps the default handler and checks if the RPC service was stopped. In that case it returns an +// error indicating that the service was stopped. This will only happen for connections which are +// kept open (HTTP keep-alive) when the RPC service was shutdown. +func newStoppableHandler(h http.Handler, stop chan struct{}) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + select { + case <-stop: + w.Header().Set("Content-Type", "application/json") + err := fmt.Errorf("RPC service stopped") + response := shared.NewRpcResponse(-1, shared.JsonRpcVersion, nil, err) + httpSend(w, response) + default: + h.ServeHTTP(w, r) + } + }) +} + +func httpSend(writer io.Writer, v interface{}) (n int, err error) { + var payload []byte + payload, err = json.MarshalIndent(v, "", "\t") + if err != nil { + glog.V(logger.Error).Infoln("Error marshalling JSON", err) + return 0, err + } + glog.V(logger.Detail).Infof("Sending payload: %s", payload) + + return writer.Write(payload) +} + +func gethHttpHandler(codec codec.Codec, a shared.EthereumApi) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + + // Limit request size to resist DoS + if req.ContentLength > maxHttpSizeReqLength { + err := fmt.Errorf("Request too large") + response := shared.NewRpcErrorResponse(-1, shared.JsonRpcVersion, -32700, err) + httpSend(w, &response) + return + } + + defer req.Body.Close() + payload, err := ioutil.ReadAll(req.Body) + if err != nil { + err := fmt.Errorf("Could not read request body") + response := shared.NewRpcErrorResponse(-1, shared.JsonRpcVersion, -32700, err) + httpSend(w, &response) + return + } + + c := codec.New(nil) + var rpcReq shared.Request + if err = c.Decode(payload, &rpcReq); err == nil { + reply, err := a.Execute(&rpcReq) + res := shared.NewRpcResponse(rpcReq.Id, rpcReq.Jsonrpc, reply, err) + httpSend(w, &res) + return + } + + var reqBatch []shared.Request + if err = c.Decode(payload, &reqBatch); err == nil { + resBatch := make([]*interface{}, len(reqBatch)) + resCount := 0 + + for i, rpcReq := range reqBatch { + reply, err := a.Execute(&rpcReq) + if rpcReq.Id != nil { // this leaves nil entries in the response batch for later removal + resBatch[i] = shared.NewRpcResponse(rpcReq.Id, rpcReq.Jsonrpc, reply, err) + resCount += 1 + } + } + + // make response omitting nil entries + resBatch = resBatch[:resCount] + httpSend(w, resBatch) + return + } + + // invalid request + err = fmt.Errorf("Could not decode request") + res := shared.NewRpcErrorResponse(-1, shared.JsonRpcVersion, -32600, err) + httpSend(w, res) + }) +} diff --git a/rpc/comms/inproc.go b/rpc/comms/inproc.go new file mode 100644 index 000000000..5c84b8fd8 --- /dev/null +++ b/rpc/comms/inproc.go @@ -0,0 +1,66 @@ +package comms + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" +) + +type InProcClient struct { + api shared.EthereumApi + codec codec.Codec + lastId interface{} + lastJsonrpc string + lastErr error + lastRes interface{} +} + +// Create a new in process client +func NewInProcClient(codec codec.Codec) *InProcClient { + return &InProcClient{ + codec: codec, + } +} + +func (self *InProcClient) Close() { + // do nothing +} + +// Need to setup api support +func (self *InProcClient) Initialize(offeredApi shared.EthereumApi) { + self.api = offeredApi +} + +func (self *InProcClient) Send(req interface{}) error { + if r, ok := req.(*shared.Request); ok { + self.lastId = r.Id + self.lastJsonrpc = r.Jsonrpc + self.lastRes, self.lastErr = self.api.Execute(r) + return self.lastErr + } + + return fmt.Errorf("Invalid request (%T)", req) +} + +func (self *InProcClient) Recv() (interface{}, error) { + return self.lastRes, self.lastErr +} + +func (self *InProcClient) SupportedModules() (map[string]string, error) { + req := shared.Request{ + Id: 1, + Jsonrpc: "2.0", + Method: "modules", + } + + if res, err := self.api.Execute(&req); err == nil { + if result, ok := res.(map[string]string); ok { + return result, nil + } + } else { + return nil, err + } + + return nil, fmt.Errorf("Invalid response") +} diff --git a/rpc/comms/ipc.go b/rpc/comms/ipc.go index a75039d17..068a1288f 100644 --- a/rpc/comms/ipc.go +++ b/rpc/comms/ipc.go @@ -1,8 +1,13 @@ package comms import ( - "github.com/ethereum/go-ethereum/rpc/api" + "fmt" + "net" + + "encoding/json" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/shared" ) type IpcConfig struct { @@ -10,19 +15,74 @@ type IpcConfig struct { } type ipcClient struct { - c codec.ApiCoder + endpoint string + codec codec.Codec + coder codec.ApiCoder } func (self *ipcClient) Close() { - self.c.Close() + self.coder.Close() } func (self *ipcClient) Send(req interface{}) error { - return self.c.WriteResponse(req) + var err error + if r, ok := req.(*shared.Request); ok { + if err = self.coder.WriteResponse(r); err != nil { + if _, ok := err.(*net.OpError); ok { // connection lost, retry once + if err = self.reconnect(); err == nil { + err = self.coder.WriteResponse(r) + } + } + } + return err + } + + return fmt.Errorf("Invalid request (%T)", req) } func (self *ipcClient) Recv() (interface{}, error) { - return self.c.ReadResponse() + res, err := self.coder.ReadResponse() + if err != nil { + return nil, err + } + + if r, ok := res.(shared.SuccessResponse); ok { + return r.Result, nil + } + + if r, ok := res.(shared.ErrorResponse); ok { + return r.Error, nil + } + + return res, err +} + +func (self *ipcClient) SupportedModules() (map[string]string, error) { + req := shared.Request{ + Id: 1, + Jsonrpc: "2.0", + Method: "modules", + } + + if err := self.coder.WriteResponse(req); err != nil { + return nil, err + } + + res, err := self.coder.ReadResponse() + if err != nil { + return nil, err + } + + if sucRes, ok := res.(shared.SuccessResponse); ok { + data, _ := json.Marshal(sucRes.Result) + modules := make(map[string]string) + err = json.Unmarshal(data, &modules) + if err == nil { + return modules, nil + } + } + + return nil, fmt.Errorf("Invalid response") } // Create a new IPC client, UNIX domain socket on posix, named pipe on Windows @@ -31,7 +91,6 @@ func NewIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { } // Start IPC server -func StartIpc(cfg IpcConfig, codec codec.Codec, apis ...api.EthereumApi) error { - offeredApi := api.Merge(apis...) +func StartIpc(cfg IpcConfig, codec codec.Codec, offeredApi shared.EthereumApi) error { return startIpc(cfg, codec, offeredApi) } diff --git a/rpc/comms/ipc_unix.go b/rpc/comms/ipc_unix.go index 5a94fd1e0..295eb916b 100644 --- a/rpc/comms/ipc_unix.go +++ b/rpc/comms/ipc_unix.go @@ -3,13 +3,11 @@ package comms import ( - "io" "net" "os" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/shared" ) @@ -20,10 +18,20 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { return nil, err } - return &ipcClient{codec.New(c)}, nil + return &ipcClient{cfg.Endpoint, codec, codec.New(c)}, nil } -func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error { +func (self *ipcClient) reconnect() error { + self.coder.Close() + c, err := net.DialUnix("unix", nil, &net.UnixAddr{self.endpoint, "unix"}) + if err == nil { + self.coder = self.codec.New(c) + } + + return err +} + +func startIpc(cfg IpcConfig, codec codec.Codec, api shared.EthereumApi) error { os.Remove(cfg.Endpoint) // in case it still exists from a previous run l, err := net.ListenUnix("unix", &net.UnixAddr{Name: cfg.Endpoint, Net: "unix"}) @@ -40,32 +48,7 @@ func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error { continue } - go func(conn net.Conn) { - codec := codec.New(conn) - - for { - req, err := codec.ReadRequest() - if err == io.EOF { - codec.Close() - return - } else if err != nil { - glog.V(logger.Error).Infof("IPC recv err - %v\n", err) - codec.Close() - return - } - - var rpcResponse interface{} - res, err := api.Execute(req) - - rpcResponse = shared.NewRpcResponse(req.Id, req.Jsonrpc, res, err) - err = codec.WriteResponse(rpcResponse) - if err != nil { - glog.V(logger.Error).Infof("IPC send err - %v\n", err) - codec.Close() - return - } - } - }(conn) + go handle(conn, api, codec) } os.Remove(cfg.Endpoint) diff --git a/rpc/comms/ipc_windows.go b/rpc/comms/ipc_windows.go index c48dfb7fb..44c82ef8a 100644 --- a/rpc/comms/ipc_windows.go +++ b/rpc/comms/ipc_windows.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/rpc/api" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/shared" ) @@ -641,10 +640,18 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { return nil, err } - return &ipcClient{codec.New(c)}, nil + return &ipcClient{cfg.Endpoint, codec, codec.New(c)}, nil } -func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error { +func (self *ipcClient) reconnect() error { + c, err := Dial(self.endpoint) + if err == nil { + self.coder = self.codec.New(c) + } + return err +} + +func startIpc(cfg IpcConfig, codec codec.Codec, api shared.EthereumApi) error { os.Remove(cfg.Endpoint) // in case it still exists from a previous run l, err := Listen(cfg.Endpoint) diff --git a/rpc/http.go b/rpc/http.go deleted file mode 100644 index f37e102f5..000000000 --- a/rpc/http.go +++ /dev/null @@ -1,163 +0,0 @@ -package rpc - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "strings" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/xeth" - "github.com/rs/cors" -) - -var rpclistener *stoppableTCPListener - -const ( - jsonrpcver = "2.0" - maxSizeReqLength = 1024 * 1024 // 1MB -) - -func Start(pipe *xeth.XEth, config RpcConfig) error { - if rpclistener != nil { - if fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort) != rpclistener.Addr().String() { - return fmt.Errorf("RPC service already running on %s ", rpclistener.Addr().String()) - } - return nil // RPC service already running on given host/port - } - - l, err := newStoppableTCPListener(fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort)) - if err != nil { - glog.V(logger.Error).Infof("Can't listen on %s:%d: %v", config.ListenAddress, config.ListenPort, err) - return err - } - rpclistener = l - - var handler http.Handler - if len(config.CorsDomain) > 0 { - var opts cors.Options - opts.AllowedMethods = []string{"POST"} - opts.AllowedOrigins = strings.Split(config.CorsDomain, " ") - - c := cors.New(opts) - handler = newStoppableHandler(c.Handler(JSONRPC(pipe)), l.stop) - } else { - handler = newStoppableHandler(JSONRPC(pipe), l.stop) - } - - go http.Serve(l, handler) - - return nil -} - -func Stop() error { - if rpclistener != nil { - rpclistener.Stop() - rpclistener = nil - } - - return nil -} - -// JSONRPC returns a handler that implements the Ethereum JSON-RPC API. -func JSONRPC(pipe *xeth.XEth) http.Handler { - api := NewEthereumApi(pipe) - - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json") - - // Limit request size to resist DoS - if req.ContentLength > maxSizeReqLength { - jsonerr := &RpcErrorObject{-32700, "Request too large"} - send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) - return - } - - // Read request body - defer req.Body.Close() - body, err := ioutil.ReadAll(req.Body) - if err != nil { - jsonerr := &RpcErrorObject{-32700, "Could not read request body"} - send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) - } - - // Try to parse the request as a single - var reqSingle RpcRequest - if err := json.Unmarshal(body, &reqSingle); err == nil { - response := RpcResponse(api, &reqSingle) - if reqSingle.Id != nil { - send(w, &response) - } - return - } - - // Try to parse the request to batch - var reqBatch []RpcRequest - if err := json.Unmarshal(body, &reqBatch); err == nil { - // Build response batch - resBatch := make([]*interface{}, len(reqBatch)) - resCount := 0 - - for i, request := range reqBatch { - response := RpcResponse(api, &request) - // this leaves nil entries in the response batch for later removal - if request.Id != nil { - resBatch[i] = response - resCount = resCount + 1 - } - } - - // make response omitting nil entries - respBatchComp := make([]*interface{}, resCount) - for _, v := range resBatch { - if v != nil { - respBatchComp[len(respBatchComp)-resCount] = v - resCount = resCount - 1 - } - } - - send(w, respBatchComp) - return - } - - // Not a batch or single request, error - jsonerr := &RpcErrorObject{-32600, "Could not decode request"} - send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) - }) -} - -func RpcResponse(api *EthereumApi, request *RpcRequest) *interface{} { - var reply, response interface{} - reserr := api.GetRequestReply(request, &reply) - switch reserr.(type) { - case nil: - response = &RpcSuccessResponse{Jsonrpc: jsonrpcver, Id: request.Id, Result: reply} - case *NotImplementedError, *NotAvailableError: - jsonerr := &RpcErrorObject{-32601, reserr.Error()} - response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr} - case *DecodeParamError, *InsufficientParamsError, *ValidationError, *InvalidTypeError: - jsonerr := &RpcErrorObject{-32602, reserr.Error()} - response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr} - default: - jsonerr := &RpcErrorObject{-32603, reserr.Error()} - response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr} - } - - glog.V(logger.Detail).Infof("Generated response: %T %s", response, response) - return &response -} - -func send(writer io.Writer, v interface{}) (n int, err error) { - var payload []byte - payload, err = json.MarshalIndent(v, "", "\t") - if err != nil { - glog.V(logger.Error).Infoln("Error marshalling JSON", err) - return 0, err - } - glog.V(logger.Detail).Infof("Sending payload: %s", payload) - - return writer.Write(payload) -} diff --git a/rpc/jeth.go b/rpc/jeth.go index e578775bb..33fcd6efd 100644 --- a/rpc/jeth.go +++ b/rpc/jeth.go @@ -2,30 +2,26 @@ package rpc import ( "encoding/json" - "fmt" - - "reflect" "github.com/ethereum/go-ethereum/jsre" - "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/shared" "github.com/robertkrimen/otto" ) type Jeth struct { - ethApi *EthereumApi - re *jsre.JSRE - ipcpath string + ethApi shared.EthereumApi + re *jsre.JSRE + client comms.EthereumClient } -func NewJeth(ethApi *EthereumApi, re *jsre.JSRE, ipcpath string) *Jeth { - return &Jeth{ethApi, re, ipcpath} +func NewJeth(ethApi shared.EthereumApi, re *jsre.JSRE, client comms.EthereumClient) *Jeth { + return &Jeth{ethApi, re, client} } func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { - rpcerr := &RpcErrorObject{code, msg} - call.Otto.Set("ret_jsonrpc", jsonrpcver) + rpcerr := &shared.ErrorObject{code, msg} + call.Otto.Set("ret_jsonrpc", shared.JsonRpcVersion) call.Otto.Set("ret_id", id) call.Otto.Set("ret_error", rpcerr) response, _ = call.Otto.Run(` @@ -41,11 +37,11 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { } jsonreq, err := json.Marshal(reqif) - var reqs []RpcRequest + var reqs []shared.Request batch := true err = json.Unmarshal(jsonreq, &reqs) if err != nil { - reqs = make([]RpcRequest, 1) + reqs = make([]shared.Request, 1) err = json.Unmarshal(jsonreq, &reqs[0]) batch = false } @@ -55,12 +51,16 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { for i, req := range reqs { var respif interface{} - err = self.ethApi.GetRequestReply(&req, &respif) + err := self.client.Send(&req) + if err != nil { + return self.err(call, -32603, err.Error(), req.Id) + } + respif, err = self.client.Recv() if err != nil { - fmt.Println("Error response:", err) return self.err(call, -32603, err.Error(), req.Id) } - call.Otto.Set("ret_jsonrpc", jsonrpcver) + + call.Otto.Set("ret_jsonrpc", shared.JsonRpcVersion) call.Otto.Set("ret_id", req.Id) res, _ := json.Marshal(respif) @@ -87,85 +87,3 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { return } - -func (self *Jeth) SendIpc(call otto.FunctionCall) (response otto.Value) { - reqif, err := call.Argument(0).Export() - if err != nil { - return self.err(call, -32700, err.Error(), nil) - } - - client, err := comms.NewIpcClient(comms.IpcConfig{self.ipcpath}, codec.JSON) - if err != nil { - fmt.Println("Unable to connect to geth.") - return self.err(call, -32603, err.Error(), -1) - } - defer client.Close() - - jsonreq, err := json.Marshal(reqif) - var reqs []RpcRequest - batch := true - err = json.Unmarshal(jsonreq, &reqs) - if err != nil { - reqs = make([]RpcRequest, 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 := client.Send(&req) - if err != nil { - fmt.Println("Error send request:", err) - return self.err(call, -32603, err.Error(), req.Id) - } - - respif, err := client.Recv() - if err != nil { - fmt.Println("Error recv response:", err) - return self.err(call, -32603, err.Error(), req.Id) - } - - if res, ok := respif.(shared.SuccessResponse); ok { - call.Otto.Set("ret_id", res.Id) - call.Otto.Set("ret_jsonrpc", res.Jsonrpc) - resObj, _ := json.Marshal(res.Result) - call.Otto.Set("ret_result", string(resObj)) - 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) }; - `) - } else if res, ok := respif.(shared.ErrorResponse); ok { - fmt.Printf("Error: %s (%d)\n", res.Error.Message, res.Error.Code) - - call.Otto.Set("ret_id", res.Id) - call.Otto.Set("ret_jsonrpc", res.Jsonrpc) - call.Otto.Set("ret_error", res.Error) - call.Otto.Set("response_idx", i) - - response, _ = call.Otto.Run(` - ret_response = { jsonrpc: ret_jsonrpc, id: ret_id, error: ret_error }; - `) - return - } else { - fmt.Printf("unexpected response\n", reflect.TypeOf(respif)) - } - } - - 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 -} diff --git a/rpc/responses.go b/rpc/responses.go deleted file mode 100644 index 9fdf60c02..000000000 --- a/rpc/responses.go +++ /dev/null @@ -1,316 +0,0 @@ -package rpc - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" -) - -type BlockRes struct { - fullTx bool - - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - TotalDifficulty *hexnum `json:"totalDifficulty"` - Size *hexnum `json:"size"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` - Transactions []*TransactionRes `json:"transactions"` - Uncles []*UncleRes `json:"uncles"` -} - -func (b *BlockRes) MarshalJSON() ([]byte, error) { - if b.fullTx { - var ext struct { - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - TotalDifficulty *hexnum `json:"totalDifficulty"` - Size *hexnum `json:"size"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` - Transactions []*TransactionRes `json:"transactions"` - Uncles []*hexdata `json:"uncles"` - } - - ext.BlockNumber = b.BlockNumber - ext.BlockHash = b.BlockHash - ext.ParentHash = b.ParentHash - ext.Nonce = b.Nonce - ext.Sha3Uncles = b.Sha3Uncles - ext.LogsBloom = b.LogsBloom - ext.TransactionRoot = b.TransactionRoot - ext.StateRoot = b.StateRoot - ext.Miner = b.Miner - ext.Difficulty = b.Difficulty - ext.TotalDifficulty = b.TotalDifficulty - ext.Size = b.Size - ext.ExtraData = b.ExtraData - ext.GasLimit = b.GasLimit - ext.GasUsed = b.GasUsed - ext.UnixTimestamp = b.UnixTimestamp - ext.Transactions = b.Transactions - ext.Uncles = make([]*hexdata, len(b.Uncles)) - for i, u := range b.Uncles { - ext.Uncles[i] = u.BlockHash - } - return json.Marshal(ext) - } else { - var ext struct { - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - TotalDifficulty *hexnum `json:"totalDifficulty"` - Size *hexnum `json:"size"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` - Transactions []*hexdata `json:"transactions"` - Uncles []*hexdata `json:"uncles"` - } - - ext.BlockNumber = b.BlockNumber - ext.BlockHash = b.BlockHash - ext.ParentHash = b.ParentHash - ext.Nonce = b.Nonce - ext.Sha3Uncles = b.Sha3Uncles - ext.LogsBloom = b.LogsBloom - ext.TransactionRoot = b.TransactionRoot - ext.StateRoot = b.StateRoot - ext.Miner = b.Miner - ext.Difficulty = b.Difficulty - ext.TotalDifficulty = b.TotalDifficulty - ext.Size = b.Size - ext.ExtraData = b.ExtraData - ext.GasLimit = b.GasLimit - ext.GasUsed = b.GasUsed - ext.UnixTimestamp = b.UnixTimestamp - ext.Transactions = make([]*hexdata, len(b.Transactions)) - for i, tx := range b.Transactions { - ext.Transactions[i] = tx.Hash - } - ext.Uncles = make([]*hexdata, len(b.Uncles)) - for i, u := range b.Uncles { - ext.Uncles[i] = u.BlockHash - } - return json.Marshal(ext) - } -} - -func NewBlockRes(block *types.Block, fullTx bool) *BlockRes { - if block == nil { - return nil - } - - res := new(BlockRes) - res.fullTx = fullTx - res.BlockNumber = newHexNum(block.Number()) - res.BlockHash = newHexData(block.Hash()) - res.ParentHash = newHexData(block.ParentHash()) - res.Nonce = newHexData(block.Nonce()) - res.Sha3Uncles = newHexData(block.Header().UncleHash) - res.LogsBloom = newHexData(block.Bloom()) - res.TransactionRoot = newHexData(block.Header().TxHash) - res.StateRoot = newHexData(block.Root()) - res.Miner = newHexData(block.Header().Coinbase) - res.Difficulty = newHexNum(block.Difficulty()) - res.TotalDifficulty = newHexNum(block.Td) - res.Size = newHexNum(block.Size().Int64()) - res.ExtraData = newHexData(block.Header().Extra) - res.GasLimit = newHexNum(block.GasLimit()) - res.GasUsed = newHexNum(block.GasUsed()) - res.UnixTimestamp = newHexNum(block.Time()) - - res.Transactions = make([]*TransactionRes, len(block.Transactions())) - for i, tx := range block.Transactions() { - res.Transactions[i] = NewTransactionRes(tx) - res.Transactions[i].BlockHash = res.BlockHash - res.Transactions[i].BlockNumber = res.BlockNumber - res.Transactions[i].TxIndex = newHexNum(i) - } - - res.Uncles = make([]*UncleRes, len(block.Uncles())) - for i, uncle := range block.Uncles() { - res.Uncles[i] = NewUncleRes(uncle) - } - - return res -} - -type TransactionRes struct { - Hash *hexdata `json:"hash"` - Nonce *hexnum `json:"nonce"` - BlockHash *hexdata `json:"blockHash"` - BlockNumber *hexnum `json:"blockNumber"` - TxIndex *hexnum `json:"transactionIndex"` - From *hexdata `json:"from"` - To *hexdata `json:"to"` - Value *hexnum `json:"value"` - Gas *hexnum `json:"gas"` - GasPrice *hexnum `json:"gasPrice"` - Input *hexdata `json:"input"` -} - -func NewTransactionRes(tx *types.Transaction) *TransactionRes { - if tx == nil { - return nil - } - - var v = new(TransactionRes) - v.Hash = newHexData(tx.Hash()) - v.Nonce = newHexNum(tx.Nonce()) - // v.BlockHash = - // v.BlockNumber = - // v.TxIndex = - from, _ := tx.From() - v.From = newHexData(from) - v.To = newHexData(tx.To()) - v.Value = newHexNum(tx.Value()) - v.Gas = newHexNum(tx.Gas()) - v.GasPrice = newHexNum(tx.GasPrice()) - v.Input = newHexData(tx.Data()) - return v -} - -type UncleRes struct { - BlockNumber *hexnum `json:"number"` - BlockHash *hexdata `json:"hash"` - ParentHash *hexdata `json:"parentHash"` - Nonce *hexdata `json:"nonce"` - Sha3Uncles *hexdata `json:"sha3Uncles"` - ReceiptHash *hexdata `json:"receiptHash"` - LogsBloom *hexdata `json:"logsBloom"` - TransactionRoot *hexdata `json:"transactionsRoot"` - StateRoot *hexdata `json:"stateRoot"` - Miner *hexdata `json:"miner"` - Difficulty *hexnum `json:"difficulty"` - ExtraData *hexdata `json:"extraData"` - GasLimit *hexnum `json:"gasLimit"` - GasUsed *hexnum `json:"gasUsed"` - UnixTimestamp *hexnum `json:"timestamp"` -} - -func NewUncleRes(h *types.Header) *UncleRes { - if h == nil { - return nil - } - - var v = new(UncleRes) - v.BlockNumber = newHexNum(h.Number) - v.BlockHash = newHexData(h.Hash()) - v.ParentHash = newHexData(h.ParentHash) - v.Sha3Uncles = newHexData(h.UncleHash) - v.Nonce = newHexData(h.Nonce[:]) - v.LogsBloom = newHexData(h.Bloom) - v.TransactionRoot = newHexData(h.TxHash) - v.StateRoot = newHexData(h.Root) - v.Miner = newHexData(h.Coinbase) - v.Difficulty = newHexNum(h.Difficulty) - v.ExtraData = newHexData(h.Extra) - v.GasLimit = newHexNum(h.GasLimit) - v.GasUsed = newHexNum(h.GasUsed) - v.UnixTimestamp = newHexNum(h.Time) - v.ReceiptHash = newHexData(h.ReceiptHash) - - return v -} - -// type FilterLogRes struct { -// Hash string `json:"hash"` -// Address string `json:"address"` -// Data string `json:"data"` -// BlockNumber string `json:"blockNumber"` -// TransactionHash string `json:"transactionHash"` -// BlockHash string `json:"blockHash"` -// TransactionIndex string `json:"transactionIndex"` -// LogIndex string `json:"logIndex"` -// } - -// type FilterWhisperRes struct { -// Hash string `json:"hash"` -// From string `json:"from"` -// To string `json:"to"` -// Expiry string `json:"expiry"` -// Sent string `json:"sent"` -// Ttl string `json:"ttl"` -// Topics string `json:"topics"` -// Payload string `json:"payload"` -// WorkProved string `json:"workProved"` -// } - -type LogRes struct { - Address *hexdata `json:"address"` - Topics []*hexdata `json:"topics"` - Data *hexdata `json:"data"` - BlockNumber *hexnum `json:"blockNumber"` - LogIndex *hexnum `json:"logIndex"` - BlockHash *hexdata `json:"blockHash"` - TransactionHash *hexdata `json:"transactionHash"` - TransactionIndex *hexnum `json:"transactionIndex"` -} - -func NewLogRes(log *state.Log) LogRes { - var l LogRes - l.Topics = make([]*hexdata, len(log.Topics)) - for j, topic := range log.Topics { - l.Topics[j] = newHexData(topic) - } - l.Address = newHexData(log.Address) - l.Data = newHexData(log.Data) - l.BlockNumber = newHexNum(log.Number) - l.LogIndex = newHexNum(log.Index) - l.TransactionHash = newHexData(log.TxHash) - l.TransactionIndex = newHexNum(log.TxIndex) - l.BlockHash = newHexData(log.BlockHash) - - return l -} - -func NewLogsRes(logs state.Logs) (ls []LogRes) { - ls = make([]LogRes, len(logs)) - - for i, log := range logs { - ls[i] = NewLogRes(log) - } - - return -} - -func NewHashesRes(hs []common.Hash) []string { - hashes := make([]string, len(hs)) - - for i, hash := range hs { - hashes[i] = hash.Hex() - } - - return hashes -} diff --git a/rpc/responses_test.go b/rpc/responses_test.go deleted file mode 100644 index 66323e5f5..000000000 --- a/rpc/responses_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package rpc - -import ( - "encoding/json" - "fmt" - "math/big" - "regexp" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" -) - -const ( - reHash = `"0x[0-9a-f]{64}"` // 32 bytes - reHashOpt = `"(0x[0-9a-f]{64})"|null` // 32 bytes or null - reAddress = `"0x[0-9a-f]{40}"` // 20 bytes - reAddressOpt = `"0x[0-9a-f]{40}"|null` // 20 bytes or null - reNum = `"0x([1-9a-f][0-9a-f]{0,15})|0"` // must not have left-padded zeros - reNumNonZero = `"0x([1-9a-f][0-9a-f]{0,15})"` // non-zero required must not have left-padded zeros - reNumOpt = `"0x([1-9a-f][0-9a-f]{0,15})|0"|null` // must not have left-padded zeros or null - reData = `"0x[0-9a-f]*"` // can be "empty" - // reListHash = `[("\w":"0x[0-9a-f]{64}",?)*]` - // reListObj = `[("\w":(".+"|null),?)*]` -) - -func TestNewBlockRes(t *testing.T) { - tests := map[string]string{ - "number": reNum, - "hash": reHash, - "parentHash": reHash, - "nonce": reData, - "sha3Uncles": reHash, - "logsBloom": reData, - "transactionsRoot": reHash, - "stateRoot": reHash, - "miner": reAddress, - "difficulty": `"0x1"`, - "totalDifficulty": reNum, - "size": reNumNonZero, - "extraData": reData, - "gasLimit": reNum, - // "minGasPrice": "0x", - "gasUsed": reNum, - "timestamp": reNum, - // "transactions": reListHash, - // "uncles": reListHash, - } - - block := makeBlock() - v := NewBlockRes(block, false) - j, _ := json.Marshal(v) - - for k, re := range tests { - match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) - if !match { - t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j)) - } - } -} - -func TestNewBlockResTxFull(t *testing.T) { - tests := map[string]string{ - "number": reNum, - "hash": reHash, - "parentHash": reHash, - "nonce": reData, - "sha3Uncles": reHash, - "logsBloom": reData, - "transactionsRoot": reHash, - "stateRoot": reHash, - "miner": reAddress, - "difficulty": `"0x1"`, - "totalDifficulty": reNum, - "size": reNumNonZero, - "extraData": reData, - "gasLimit": reNum, - // "minGasPrice": "0x", - "gasUsed": reNum, - "timestamp": reNum, - // "transactions": reListHash, - // "uncles": reListHash, - } - - block := makeBlock() - v := NewBlockRes(block, true) - j, _ := json.Marshal(v) - - for k, re := range tests { - match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) - if !match { - t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j)) - } - } -} - -func TestBlockNil(t *testing.T) { - var block *types.Block - block = nil - u := NewBlockRes(block, false) - j, _ := json.Marshal(u) - if string(j) != "null" { - t.Errorf("Expected null but got %v", string(j)) - } -} - -func TestNewTransactionRes(t *testing.T) { - to := common.HexToAddress("0x02") - amount := big.NewInt(1) - gasAmount := big.NewInt(1) - gasPrice := big.NewInt(1) - data := []byte{1, 2, 3} - tx := types.NewTransactionMessage(to, amount, gasAmount, gasPrice, data) - - tests := map[string]string{ - "hash": reHash, - "nonce": reNum, - "blockHash": reHashOpt, - "blockNum": reNumOpt, - "transactionIndex": reNumOpt, - "from": reAddress, - "to": reAddressOpt, - "value": reNum, - "gas": reNum, - "gasPrice": reNum, - "input": reData, - } - - v := NewTransactionRes(tx) - v.BlockHash = newHexData(common.HexToHash("0x030201")) - v.BlockNumber = newHexNum(5) - v.TxIndex = newHexNum(0) - j, _ := json.Marshal(v) - for k, re := range tests { - match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) - if !match { - t.Error(fmt.Sprintf("`%s` output json does not match format %s. Source %s", k, re, j)) - } - } - -} - -func TestTransactionNil(t *testing.T) { - var tx *types.Transaction - tx = nil - u := NewTransactionRes(tx) - j, _ := json.Marshal(u) - if string(j) != "null" { - t.Errorf("Expected null but got %v", string(j)) - } -} - -func TestNewUncleRes(t *testing.T) { - header := makeHeader() - u := NewUncleRes(header) - tests := map[string]string{ - "number": reNum, - "hash": reHash, - "parentHash": reHash, - "nonce": reData, - "sha3Uncles": reHash, - "receiptHash": reHash, - "transactionsRoot": reHash, - "stateRoot": reHash, - "miner": reAddress, - "difficulty": reNum, - "extraData": reData, - "gasLimit": reNum, - "gasUsed": reNum, - "timestamp": reNum, - } - - j, _ := json.Marshal(u) - for k, re := range tests { - match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) - if !match { - t.Error(fmt.Sprintf("`%s` output json does not match format %s. Source %s", k, re, j)) - } - } -} - -func TestUncleNil(t *testing.T) { - var header *types.Header - header = nil - u := NewUncleRes(header) - j, _ := json.Marshal(u) - if string(j) != "null" { - t.Errorf("Expected null but got %v", string(j)) - } -} - -func TestNewLogRes(t *testing.T) { - log := makeStateLog(0) - tests := map[string]string{ - "address": reAddress, - // "topics": "[.*]" - "data": reData, - "blockNumber": reNum, - // "hash": reHash, - // "logIndex": reNum, - // "blockHash": reHash, - // "transactionHash": reHash, - "transactionIndex": reNum, - } - - v := NewLogRes(log) - j, _ := json.Marshal(v) - - for k, re := range tests { - match, _ := regexp.MatchString(fmt.Sprintf(`{.*"%s":%s.*}`, k, re), string(j)) - if !match { - t.Error(fmt.Sprintf("`%s` output json does not match format %s. Got %s", k, re, j)) - } - } - -} - -func TestNewLogsRes(t *testing.T) { - logs := make([]*state.Log, 3) - logs[0] = makeStateLog(1) - logs[1] = makeStateLog(2) - logs[2] = makeStateLog(3) - tests := map[string]string{} - - v := NewLogsRes(logs) - j, _ := json.Marshal(v) - - for k, re := range tests { - match, _ := regexp.MatchString(fmt.Sprintf(`[{.*"%s":%s.*}]`, k, re), string(j)) - if !match { - t.Error(fmt.Sprintf("%s output json does not match format %s. Got %s", k, re, j)) - } - } - -} - -func makeStateLog(num int) *state.Log { - address := common.HexToAddress("0x0") - data := []byte{1, 2, 3} - number := uint64(num) - topics := make([]common.Hash, 3) - topics = append(topics, common.HexToHash("0x00")) - topics = append(topics, common.HexToHash("0x10")) - topics = append(topics, common.HexToHash("0x20")) - log := state.NewLog(address, topics, data, number) - return log -} - -func makeHeader() *types.Header { - header := &types.Header{ - ParentHash: common.StringToHash("0x00"), - UncleHash: common.StringToHash("0x00"), - Coinbase: common.StringToAddress("0x00"), - Root: common.StringToHash("0x00"), - TxHash: common.StringToHash("0x00"), - ReceiptHash: common.StringToHash("0x00"), - // Bloom: - Difficulty: big.NewInt(88888888), - Number: big.NewInt(16), - GasLimit: big.NewInt(70000), - GasUsed: big.NewInt(25000), - Time: 124356789, - Extra: nil, - MixDigest: common.StringToHash("0x00"), - Nonce: [8]byte{0, 1, 2, 3, 4, 5, 6, 7}, - } - return header -} - -func makeBlock() *types.Block { - parentHash := common.HexToHash("0x01") - coinbase := common.HexToAddress("0x01") - root := common.HexToHash("0x01") - difficulty := common.Big1 - nonce := uint64(1) - block := types.NewBlock(parentHash, coinbase, root, difficulty, nonce, nil) - - txto := common.HexToAddress("0x02") - txamount := big.NewInt(1) - txgasAmount := big.NewInt(1) - txgasPrice := big.NewInt(1) - txdata := []byte{1, 2, 3} - - tx := types.NewTransactionMessage(txto, txamount, txgasAmount, txgasPrice, txdata) - txs := make([]*types.Transaction, 1) - txs[0] = tx - block.SetTransactions(txs) - - uncles := make([]*types.Header, 1) - uncles[0] = makeHeader() - block.SetUncles(uncles) - - return block -} diff --git a/rpc/shared/types.go b/rpc/shared/types.go index 6a29fa88e..7c4b04e83 100644 --- a/rpc/shared/types.go +++ b/rpc/shared/types.go @@ -7,6 +7,21 @@ import ( "github.com/ethereum/go-ethereum/logger/glog" ) +// Ethereum RPC API interface +type EthereumApi interface { + // API identifier + Name() string + + // API version + ApiVersion() string + + // Execute the given request and returns the response or an error + Execute(*Request) (interface{}, error) + + // List of supported RCP methods this API provides + Methods() []string +} + // RPC request type Request struct { Id interface{} `json:"id"` @@ -42,6 +57,18 @@ type ErrorObject struct { // Data interface{} `json:"data"` } +// Create RPC error response, this allows for custom error codes +func NewRpcErrorResponse(id interface{}, jsonrpcver string, errCode int, err error) *interface{} { + var response interface{} + + jsonerr := &ErrorObject{errCode, err.Error()} + response = ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr} + + glog.V(logger.Detail).Infof("Generated error response: %s", response) + return &response +} + +// Create RPC response func NewRpcResponse(id interface{}, jsonrpcver string, reply interface{}, err error) *interface{} { var response interface{} diff --git a/rpc/shared/utils.go b/rpc/shared/utils.go new file mode 100644 index 000000000..e5d6ad417 --- /dev/null +++ b/rpc/shared/utils.go @@ -0,0 +1,27 @@ +package shared + +import "strings" + +const ( + AdminApiName = "admin" + EthApiName = "eth" + DbApiName = "db" + DebugApiName = "debug" + MergedApiName = "merged" + MinerApiName = "miner" + NetApiName = "net" + ShhApiName = "shh" + TxPoolApiName = "txpool" + PersonalApiName = "personal" + Web3ApiName = "web3" + + JsonRpcVersion = "2.0" +) + +var ( + // All API's + AllApis = strings.Join([]string{ + AdminApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, + ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, + }, ",") +) diff --git a/rpc/types.go b/rpc/types.go deleted file mode 100644 index 1f49a3dea..000000000 --- a/rpc/types.go +++ /dev/null @@ -1,381 +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/>. -*/ -package rpc - -import ( - "encoding/binary" - "encoding/hex" - "encoding/json" - "fmt" - "math/big" - "strings" - - "errors" - "net" - "net/http" - "time" - - "io" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -type hexdata struct { - data []byte - isNil bool -} - -func (d *hexdata) String() string { - return "0x" + common.Bytes2Hex(d.data) -} - -func (d *hexdata) MarshalJSON() ([]byte, error) { - if d.isNil { - return json.Marshal(nil) - } - return json.Marshal(d.String()) -} - -func newHexData(input interface{}) *hexdata { - d := new(hexdata) - - if input == nil { - d.isNil = true - return d - } - switch input := input.(type) { - case []byte: - d.data = input - case common.Hash: - d.data = input.Bytes() - case *common.Hash: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case common.Address: - d.data = input.Bytes() - case *common.Address: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case types.Bloom: - d.data = input.Bytes() - case *types.Bloom: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case *big.Int: - if input == nil { - d.isNil = true - } else { - d.data = input.Bytes() - } - case int64: - d.data = big.NewInt(input).Bytes() - case uint64: - buff := make([]byte, 8) - binary.BigEndian.PutUint64(buff, input) - d.data = buff - case int: - d.data = big.NewInt(int64(input)).Bytes() - case uint: - d.data = big.NewInt(int64(input)).Bytes() - case int8: - d.data = big.NewInt(int64(input)).Bytes() - case uint8: - d.data = big.NewInt(int64(input)).Bytes() - case int16: - d.data = big.NewInt(int64(input)).Bytes() - case uint16: - buff := make([]byte, 2) - binary.BigEndian.PutUint16(buff, input) - d.data = buff - case int32: - d.data = big.NewInt(int64(input)).Bytes() - case uint32: - buff := make([]byte, 4) - binary.BigEndian.PutUint32(buff, input) - d.data = buff - case string: // hexstring - // aaargh ffs TODO: avoid back-and-forth hex encodings where unneeded - bytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x")) - if err != nil { - d.isNil = true - } else { - d.data = bytes - } - default: - d.isNil = true - } - - return d -} - -type hexnum struct { - data []byte - isNil bool -} - -func (d *hexnum) String() string { - // Get hex string from bytes - out := common.Bytes2Hex(d.data) - // Trim leading 0s - out = strings.TrimLeft(out, "0") - // Output "0x0" when value is 0 - if len(out) == 0 { - out = "0" - } - return "0x" + out -} - -func (d *hexnum) MarshalJSON() ([]byte, error) { - if d.isNil { - return json.Marshal(nil) - } - return json.Marshal(d.String()) -} - -func newHexNum(input interface{}) *hexnum { - d := new(hexnum) - - d.data = newHexData(input).data - - return d -} - -type RpcConfig struct { - ListenAddress string - ListenPort uint - CorsDomain string -} - -type InvalidTypeError struct { - method string - msg string -} - -func (e *InvalidTypeError) Error() string { - return fmt.Sprintf("invalid type on field %s: %s", e.method, e.msg) -} - -func NewInvalidTypeError(method, msg string) *InvalidTypeError { - return &InvalidTypeError{ - method: method, - msg: msg, - } -} - -type InsufficientParamsError struct { - have int - want int -} - -func (e *InsufficientParamsError) Error() string { - return fmt.Sprintf("insufficient params, want %d have %d", e.want, e.have) -} - -func NewInsufficientParamsError(have int, want int) *InsufficientParamsError { - return &InsufficientParamsError{ - have: have, - want: want, - } -} - -type NotImplementedError struct { - Method string -} - -func (e *NotImplementedError) Error() string { - return fmt.Sprintf("%s method not implemented", e.Method) -} - -func NewNotImplementedError(method string) *NotImplementedError { - return &NotImplementedError{ - Method: method, - } -} - -type NotAvailableError struct { - Method string - Reason string -} - -func (e *NotAvailableError) Error() string { - return fmt.Sprintf("%s method not available: %s", e.Method, e.Reason) -} - -func NewNotAvailableError(method string, reason string) *NotAvailableError { - return &NotAvailableError{ - Method: method, - Reason: reason, - } -} - -type DecodeParamError struct { - err string -} - -func (e *DecodeParamError) Error() string { - return fmt.Sprintf("could not decode, %s", e.err) - -} - -func NewDecodeParamError(errstr string) error { - return &DecodeParamError{ - err: errstr, - } -} - -type ValidationError struct { - ParamName string - msg string -} - -func (e *ValidationError) Error() string { - return fmt.Sprintf("%s not valid, %s", e.ParamName, e.msg) -} - -func NewValidationError(param string, msg string) error { - return &ValidationError{ - ParamName: param, - msg: msg, - } -} - -type RpcRequest struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params json.RawMessage `json:"params"` -} - -type RpcSuccessResponse struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Result interface{} `json:"result"` -} - -type RpcErrorResponse struct { - Id interface{} `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Error *RpcErrorObject `json:"error"` -} - -type RpcErrorObject struct { - Code int `json:"code"` - Message string `json:"message"` - // Data interface{} `json:"data"` -} - -type listenerHasStoppedError struct { - msg string -} - -func (self listenerHasStoppedError) Error() string { - return self.msg -} - -var listenerStoppedError = listenerHasStoppedError{"Listener stopped"} - -// When https://github.com/golang/go/issues/4674 is fixed this could be replaced -type stoppableTCPListener struct { - *net.TCPListener - stop chan struct{} // closed when the listener must stop -} - -// Wraps the default handler and checks if the RPC service was stopped. In that case it returns an -// error indicating that the service was stopped. This will only happen for connections which are -// kept open (HTTP keep-alive) when the RPC service was shutdown. -func newStoppableHandler(h http.Handler, stop chan struct{}) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - select { - case <-stop: - w.Header().Set("Content-Type", "application/json") - jsonerr := &RpcErrorObject{-32603, "RPC service stopped"} - send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) - default: - h.ServeHTTP(w, r) - } - }) -} - -// Stop the listener and all accepted and still active connections. -func (self *stoppableTCPListener) Stop() { - close(self.stop) -} - -func newStoppableTCPListener(addr string) (*stoppableTCPListener, error) { - wl, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - if tcpl, ok := wl.(*net.TCPListener); ok { - stop := make(chan struct{}) - l := &stoppableTCPListener{tcpl, stop} - return l, nil - } - - return nil, errors.New("Unable to create TCP listener for RPC service") -} - -func (self *stoppableTCPListener) Accept() (net.Conn, error) { - for { - self.SetDeadline(time.Now().Add(time.Duration(1 * time.Second))) - c, err := self.TCPListener.AcceptTCP() - - select { - case <-self.stop: - if c != nil { // accept timeout - c.Close() - } - self.TCPListener.Close() - return nil, listenerStoppedError - default: - } - - if err != nil { - if netErr, ok := err.(net.Error); ok && netErr.Timeout() && netErr.Temporary() { - continue // regular timeout - } - } - - return &closableConnection{c, self.stop}, err - } -} - -type closableConnection struct { - *net.TCPConn - closed chan struct{} -} - -func (self *closableConnection) Read(b []byte) (n int, err error) { - select { - case <-self.closed: - self.TCPConn.Close() - return 0, io.EOF - default: - return self.TCPConn.Read(b) - } -} diff --git a/rpc/types_test.go b/rpc/types_test.go deleted file mode 100644 index 9ef7b8d38..000000000 --- a/rpc/types_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package rpc - -import ( - "bytes" - "encoding/json" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -func TestInvalidTypeError(t *testing.T) { - err := NewInvalidTypeError("testField", "not string") - expected := "invalid type on field testField: not string" - - if err.Error() != expected { - t.Error(err.Error()) - } -} - -func TestInsufficientParamsError(t *testing.T) { - err := NewInsufficientParamsError(0, 1) - expected := "insufficient params, want 1 have 0" - - if err.Error() != expected { - t.Error(err.Error()) - } -} - -func TestNotImplementedError(t *testing.T) { - err := NewNotImplementedError("foo") - expected := "foo method not implemented" - - if err.Error() != expected { - t.Error(err.Error()) - } -} - -func TestDecodeParamError(t *testing.T) { - err := NewDecodeParamError("foo") - expected := "could not decode, foo" - - if err.Error() != expected { - t.Error(err.Error()) - } -} - -func TestValidationError(t *testing.T) { - err := NewValidationError("foo", "should be `bar`") - expected := "foo not valid, should be `bar`" - - if err.Error() != expected { - t.Error(err.Error()) - } -} - -func TestHexdataMarshalNil(t *testing.T) { - hd := newHexData([]byte{}) - hd.isNil = true - v, _ := json.Marshal(hd) - if string(v) != "null" { - t.Errorf("Expected null, got %s", v) - } -} - -func TestHexnumMarshalNil(t *testing.T) { - hn := newHexNum([]byte{}) - hn.isNil = true - v, _ := json.Marshal(hn) - if string(v) != "null" { - t.Errorf("Expected null, got %s", v) - } -} - -func TestHexdataNil(t *testing.T) { - v := newHexData(nil) - if v.isNil != true { - t.Errorf("Expected isNil to be true, but is %v", v.isNil) - } -} - -func TestHexdataPtrHash(t *testing.T) { - in := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} - v := newHexData(&in) - if bytes.Compare(in.Bytes(), v.data) != 0 { - t.Errorf("Got % x expected % x", in, v.data) - } -} - -func TestHexdataPtrHashNil(t *testing.T) { - var in *common.Hash - in = nil - v := newHexData(in) - if !v.isNil { - t.Errorf("Expect isNil to be true, but is %v", v.isNil) - } -} - -func TestHexdataPtrAddress(t *testing.T) { - in := common.Address{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19} - v := newHexData(&in) - if bytes.Compare(in.Bytes(), v.data) != 0 { - t.Errorf("Got % x expected % x", in, v.data) - } -} - -func TestHexdataPtrAddressNil(t *testing.T) { - var in *common.Address - in = nil - v := newHexData(in) - if !v.isNil { - t.Errorf("Expect isNil to be true, but is %v", v.isNil) - } -} - -func TestHexdataPtrBloom(t *testing.T) { - in := types.Bloom{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19} - v := newHexData(&in) - if bytes.Compare(in.Bytes(), v.data) != 0 { - t.Errorf("Got % x expected % x", in, v.data) - } -} - -func TestHexdataPtrBloomNil(t *testing.T) { - var in *types.Bloom - in = nil - v := newHexData(in) - if !v.isNil { - t.Errorf("Expect isNil to be true, but is %v", v.isNil) - } -} - -func TestHexdataBigintNil(t *testing.T) { - var in *big.Int - in = nil - v := newHexData(in) - if !v.isNil { - t.Errorf("Expect isNil to be true, but is %v", v.isNil) - } -} - -func TestHexdataUint(t *testing.T) { - var in = uint(16) - var expected = []byte{0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} - -func TestHexdataInt8(t *testing.T) { - var in = int8(16) - var expected = []byte{0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} - -func TestHexdataUint8(t *testing.T) { - var in = uint8(16) - var expected = []byte{0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} - -func TestHexdataInt16(t *testing.T) { - var in = int16(16) - var expected = []byte{0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} - -func TestHexdataUint16(t *testing.T) { - var in = uint16(16) - var expected = []byte{0x0, 0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} - -func TestHexdataInt32(t *testing.T) { - var in = int32(16) - var expected = []byte{0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} - -func TestHexdataUint32(t *testing.T) { - var in = uint32(16) - var expected = []byte{0x0, 0x0, 0x0, 0x10} - v := newHexData(in) - if bytes.Compare(expected, v.data) != 0 { - t.Errorf("Expected % x got % x", expected, v.data) - } -} |