Merge pull request #1279 from bas-vk/rpc-http

Integrate console and remove old rpc package structure
pull/1323/head
Jeffrey Wilcke 10 years ago
commit 6b5532ab0d
  1. 5
      Makefile
  2. 9
      cmd/console/admin.go
  3. 6
      cmd/console/contracts.go
  4. 454
      cmd/console/js.go
  5. 103
      cmd/console/main.go
  6. 937
      cmd/geth/admin.go
  7. 225
      cmd/geth/js.go
  8. 19
      cmd/geth/js_test.go
  9. 71
      cmd/geth/main.go
  10. 27
      cmd/utils/flags.go
  11. 2
      core/transaction_pool.go
  12. 646
      rpc/api.go
  13. 95
      rpc/api/admin.go
  14. 52
      rpc/api/admin_args.go
  15. 14
      rpc/api/admin_js.go
  16. 40
      rpc/api/api.go
  17. 111
      rpc/api/api_test.go
  18. 58
      rpc/api/args.go
  19. 12
      rpc/api/args_test.go
  20. 128
      rpc/api/db.go
  21. 110
      rpc/api/db_args.go
  22. 41
      rpc/api/db_js.go
  23. 2
      rpc/api/debug.go
  24. 93
      rpc/api/eth.go
  25. 48
      rpc/api/eth_args.go
  26. 17
      rpc/api/eth_js.go
  27. 12
      rpc/api/mergedapi.go
  28. 1
      rpc/api/mergedapi_js.go
  29. 2
      rpc/api/miner.go
  30. 4
      rpc/api/net.go
  31. 2
      rpc/api/personal.go
  32. 2
      rpc/api/shh.go
  33. 2
      rpc/api/txpool.go
  34. 51
      rpc/api/utils.go
  35. 2
      rpc/api/web3.go
  36. 24
      rpc/api/web3_args.go
  37. 205
      rpc/api_test.go
  38. 1230
      rpc/args.go
  39. 104
      rpc/comms/comms.go
  40. 190
      rpc/comms/http.go
  41. 166
      rpc/comms/http_net.go
  42. 66
      rpc/comms/inproc.go
  43. 73
      rpc/comms/ipc.go
  44. 43
      rpc/comms/ipc_unix.go
  45. 13
      rpc/comms/ipc_windows.go
  46. 163
      rpc/http.go
  47. 114
      rpc/jeth.go
  48. 316
      rpc/responses.go
  49. 295
      rpc/responses_test.go
  50. 27
      rpc/shared/types.go
  51. 27
      rpc/shared/utils.go
  52. 381
      rpc/types.go
  53. 204
      rpc/types_test.go

@ -10,11 +10,6 @@ geth:
@echo "Done building." @echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth." @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: mist:
build/env.sh go install -v $(shell build/ldflags.sh) ./cmd/mist build/env.sh go install -v $(shell build/ldflags.sh) ./cmd/mist
@echo "Done building." @echo "Done building."

@ -1,9 +0,0 @@
package main
/*
node admin bindings
*/
func (js *jsre) adminBindings() {
}

@ -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"
)

@ -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)
}
}

@ -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)
}
}

@ -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(),
}
}

@ -26,6 +26,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sort"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/docserver" "github.com/ethereum/go-ethereum/common/docserver"
@ -33,9 +35,13 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
re "github.com/ethereum/go-ethereum/jsre" re "github.com/ethereum/go-ethereum/jsre"
"github.com/ethereum/go-ethereum/rpc" "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/ethereum/go-ethereum/xeth"
"github.com/peterh/liner" "github.com/peterh/liner"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/ethereum/go-ethereum/rpc/shared"
) )
type prompter interface { type prompter interface {
@ -70,10 +76,104 @@ type jsre struct {
ps1 string ps1 string
atexit func() atexit func()
corsDomain string corsDomain string
client comms.EthereumClient
prompter 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: "> "} js := &jsre{ethereum: ethereum, ps1: "> "}
// set default cors domain used by startRpc from CLI flag // set default cors domain used by startRpc from CLI flag
js.corsDomain = corsDomain js.corsDomain = corsDomain
@ -82,10 +182,18 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, intera
} }
js.xeth = xeth.New(ethereum, f) js.xeth = xeth.New(ethereum, f)
js.wait = js.xeth.UpdateState() 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 // update state in separare forever blocks
js.re = re.New(libPath) js.re = re.New(libPath)
js.apiBindings(ipcpath, f) if err := js.apiBindings(f); err != nil {
js.adminBindings() utils.Fatalf("Unable to connect - %v", err)
}
if !liner.TerminalSupported() || !interactive { if !liner.TerminalSupported() || !interactive {
js.prompter = dumbterm{bufio.NewReader(os.Stdin)} js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
@ -93,6 +201,9 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, intera
lr := liner.NewLiner() lr := liner.NewLiner()
js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true) lr.SetCtrlCAborts(true)
js.loadAutoCompletion()
lr.SetWordCompleter(apiWordCompleter)
lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr js.prompter = lr
js.atexit = func() { js.atexit = func() {
js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) 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 return js
} }
func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) { func (self *jsre) loadAutoCompletion() {
xe := xeth.New(js.ethereum, f) if modules, err := self.supportedApis(); err == nil {
ethApi := rpc.NewEthereumApi(xe) loadedModulesMethods = make(map[string][]string)
jeth := rpc.NewJeth(ethApi, js.re, ipcpath) 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{}{}) js.re.Set("jeth", struct{}{})
t, _ := js.re.Get("jeth") t, _ := js.re.Get("jeth")
jethObj := t.Object() jethObj := t.Object()
@ -115,14 +291,14 @@ func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) {
jethObj.Set("send", jeth.Send) jethObj.Set("send", jeth.Send)
jethObj.Set("sendAsync", 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 { if err != nil {
utils.Fatalf("Error loading bignumber.js: %v", err) utils.Fatalf("Error loading bignumber.js: %v", err)
} }
err = js.re.Compile("ethereum.js", re.Web3_JS) err = js.re.Compile("ethereum.js", re.Web3_JS)
if err != nil { 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');") _, err = js.re.Eval("var web3 = require('web3');")
@ -134,17 +310,29 @@ func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) {
if err != nil { if err != nil {
utils.Fatalf("Error setting web3 provider: %v", err) utils.Fatalf("Error setting web3 provider: %v", err)
} }
_, err = js.re.Eval(`
var eth = web3.eth; // load only supported API's in javascript runtime
var shh = web3.shh; shortcuts := "var eth = web3.eth; "
var db = web3.db; for _, apiName := range apiNames {
var net = web3.net; 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 { if err != nil {
utils.Fatalf("Error setting namespaces: %v", err) utils.Fatalf("Error setting namespaces: %v", err)
} }
js.re.Eval(globalRegistrar + "registrar = GlobalRegistrar.at(\"" + globalRegistrarAddr + "\");") js.re.Eval(globalRegistrar + "registrar = GlobalRegistrar.at(\"" + globalRegistrarAddr + "\");")
return nil
} }
var ds, _ = docserver.New("/") var ds, _ = docserver.New("/")
@ -234,7 +422,12 @@ func (self *jsre) interactive() {
} }
func (self *jsre) withHistory(op func(*os.File)) { func (self *jsre) withHistory(op func(*os.File)) {
hist, err := os.OpenFile(filepath.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) datadir := common.DefaultDataDir()
if self.ethereum != nil {
datadir = self.ethereum.DataDir
}
hist, err := os.OpenFile(filepath.Join(datadir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil { if err != nil {
fmt.Printf("unable to open history file: %v\n", err) fmt.Printf("unable to open history file: %v\n", err)
return return

@ -20,6 +20,8 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/codec"
) )
const ( const (
@ -105,7 +107,8 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
t.Errorf("Error creating DocServer: %v", err) t.Errorf("Error creating DocServer: %v", err)
} }
tf := &testjethre{ds: ds, stateDb: ethereum.ChainManager().State().Copy()} 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 tf.jsre = repl
return tmp, tf, ethereum return tmp, tf, ethereum
} }
@ -125,7 +128,7 @@ func TestNodeInfo(t *testing.T) {
defer ethereum.Stop() defer ethereum.Stop()
defer os.RemoveAll(tmp) 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"}` 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) { func TestAccounts(t *testing.T) {
@ -139,7 +142,7 @@ func TestAccounts(t *testing.T) {
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`) checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
checkEvalJSON(t, repl, `eth.coinbase`, `"`+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 { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
@ -161,7 +164,7 @@ func TestBlockChain(t *testing.T) {
defer ethereum.Stop() defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
// get current block dump before export/import. // 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 { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
@ -178,14 +181,14 @@ func TestBlockChain(t *testing.T) {
ethereum.ChainManager().Reset() ethereum.ChainManager().Reset()
checkEvalJSON(t, repl, `admin.export(`+tmpfileq+`)`, `true`) checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`)
if _, err := os.Stat(tmpfile); err != nil { if _, err := os.Stat(tmpfile); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// check import, verify that dumpBlock gives the same result. // check import, verify that dumpBlock gives the same result.
checkEvalJSON(t, repl, `admin.import(`+tmpfileq+`)`, `true`) checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`)
checkEvalJSON(t, repl, `admin.debug.dumpBlock()`, beforeExport) checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport)
} }
func TestMining(t *testing.T) { func TestMining(t *testing.T) {
@ -207,7 +210,7 @@ func TestRPC(t *testing.T) {
defer ethereum.Stop() defer ethereum.Stop()
defer os.RemoveAll(tmp) 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) { func TestCheckTestAccountBalance(t *testing.T) {

@ -38,6 +38,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger" "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-colorable"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
) )
@ -202,6 +204,16 @@ nodes.
The Geth console is an interactive shell for the JavaScript runtime environment The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API. which exposes a node admin interface as well as the Ðapp JavaScript API.
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
`},
{
Action: attach,
Name: "attach",
Usage: `Geth Console: interactive JavaScript environment (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.RPCEnabledFlag,
utils.RPCListenAddrFlag, utils.RPCListenAddrFlag,
utils.RPCPortFlag, utils.RPCPortFlag,
utils.RpcApiFlag,
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCApiFlag, utils.IPCApiFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.ExecFlag,
utils.WhisperEnabledFlag, utils.WhisperEnabledFlag,
utils.VMDebugFlag, utils.VMDebugFlag,
utils.ProtocolVersionFlag, utils.ProtocolVersionFlag,
@ -294,6 +308,44 @@ func run(ctx *cli.Context) {
ethereum.WaitForShutdown() ethereum.WaitForShutdown()
} }
func attach(ctx *cli.Context) {
// Wrap the standard output with a colorified stream (windows)
if isatty.IsTerminal(os.Stdout.Fd()) {
if pr, pw, err := os.Pipe(); err == nil {
go io.Copy(colorable.NewColorableStdout(), pr)
os.Stdout = pw
}
}
var client comms.EthereumClient
var err error
if ctx.Args().Present() {
client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON)
} else {
cfg := comms.IpcConfig{
Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name),
}
client, err = comms.NewIpcClient(cfg, codec.JSON)
}
if err != nil {
utils.Fatalf("Unable to attach to geth node - %v", err)
}
repl := newLightweightJSRE(
ctx.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) { func console(ctx *cli.Context) {
// Wrap the standard output with a colorified stream (windows) // Wrap the standard output with a colorified stream (windows)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
@ -309,16 +361,24 @@ func console(ctx *cli.Context) {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
} }
client := comms.NewInProcClient(codec.JSON)
startEth(ctx, ethereum) startEth(ctx, ethereum)
repl := newJSRE( repl := newJSRE(
ethereum, ethereum,
ctx.String(utils.JSpathFlag.Name), ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
utils.IpcSocketPath(ctx), client,
true, true,
nil, nil,
) )
repl.interactive()
if ctx.GlobalString(utils.ExecFlag.Name) != "" {
repl.batch(ctx.GlobalString(utils.ExecFlag.Name))
} else {
repl.welcome()
repl.interactive()
}
ethereum.Stop() ethereum.Stop()
ethereum.WaitForShutdown() ethereum.WaitForShutdown()
@ -331,12 +391,13 @@ func execJSFiles(ctx *cli.Context) {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
} }
client := comms.NewInProcClient(codec.JSON)
startEth(ctx, ethereum) startEth(ctx, ethereum)
repl := newJSRE( repl := newJSRE(
ethereum, ethereum,
ctx.String(utils.JSpathFlag.Name), ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name), ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
utils.IpcSocketPath(ctx), client,
false, false,
nil, nil,
) )

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p/nat" "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/api"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms" "github.com/ethereum/go-ethereum/rpc/comms"
@ -209,20 +208,29 @@ var (
Usage: "Domain on which to send Access-Control-Allow-Origin header", Usage: "Domain on which to send Access-Control-Allow-Origin header",
Value: "", 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{ IPCDisabledFlag = cli.BoolFlag{
Name: "ipcdisable", Name: "ipcdisable",
Usage: "Disable the IPC-RPC server", Usage: "Disable the IPC-RPC server",
} }
IPCApiFlag = cli.StringFlag{ IPCApiFlag = cli.StringFlag{
Name: "ipcapi", Name: "ipcapi",
Usage: "Specify the API's which are offered over this interface", Usage: "Specify the API's which are offered over the IPC interface",
Value: api.DefaultIpcApis, Value: comms.DefaultIpcApis,
} }
IPCPathFlag = DirectoryFlag{ IPCPathFlag = DirectoryFlag{
Name: "ipcpath", Name: "ipcpath",
Usage: "Filename for IPC socket/pipe", Usage: "Filename for IPC socket/pipe",
Value: DirectoryString{common.DefaultIpcPath()}, Value: DirectoryString{common.DefaultIpcPath()},
} }
ExecFlag = cli.StringFlag{
Name: "exec",
Usage: "Execute javascript statement (only in combination with console/attach)",
}
// Network Settings // Network Settings
MaxPeersFlag = cli.IntFlag{ MaxPeersFlag = cli.IntFlag{
Name: "maxpeers", Name: "maxpeers",
@ -453,18 +461,25 @@ func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error {
return err 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 { func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error {
config := rpc.RpcConfig{ config := comms.HttpConfig{
ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name), ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name),
ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)), ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)),
CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name), CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name),
} }
xeth := xeth.New(eth, nil) 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) { func StartPProf(ctx *cli.Context) {

@ -149,7 +149,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
return ErrInvalidSender 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. // haven't got funds and well therefor never pass.
if !pool.currentState().HasAccount(from) { if !pool.currentState().HasAccount(from) {
return ErrNonExistentAccount return ErrNonExistentAccount

@ -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)
}

@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
) )
@ -23,8 +24,6 @@ const (
var ( var (
// mapping between methods and handlers // mapping between methods and handlers
AdminMapping = map[string]adminhandler{ AdminMapping = map[string]adminhandler{
// "admin_startRPC": (*adminApi).StartRPC,
// "admin_stopRPC": (*adminApi).StopRPC,
"admin_addPeer": (*adminApi).AddPeer, "admin_addPeer": (*adminApi).AddPeer,
"admin_peers": (*adminApi).Peers, "admin_peers": (*adminApi).Peers,
"admin_nodeInfo": (*adminApi).NodeInfo, "admin_nodeInfo": (*adminApi).NodeInfo,
@ -34,6 +33,8 @@ var (
"admin_chainSyncStatus": (*adminApi).ChainSyncStatus, "admin_chainSyncStatus": (*adminApi).ChainSyncStatus,
"admin_setSolc": (*adminApi).SetSolc, "admin_setSolc": (*adminApi).SetSolc,
"admin_datadir": (*adminApi).DataDir, "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 { type adminApi struct {
xeth *xeth.XEth xeth *xeth.XEth
ethereum *eth.Ethereum ethereum *eth.Ethereum
methods map[string]adminhandler codec codec.Codec
codec codec.ApiCoder coder codec.ApiCoder
} }
// create a new admin api instance // 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{ return &adminApi{
xeth: xeth, xeth: xeth,
ethereum: ethereum, ethereum: ethereum,
methods: AdminMapping, codec: codec,
codec: coder.New(nil), coder: codec.New(nil),
} }
} }
// collection with supported methods // collection with supported methods
func (self *adminApi) Methods() []string { func (self *adminApi) Methods() []string {
methods := make([]string, len(self.methods)) methods := make([]string, len(AdminMapping))
i := 0 i := 0
for k := range self.methods { for k := range AdminMapping {
methods[i] = k methods[i] = k
i++ i++
} }
@ -71,7 +72,7 @@ func (self *adminApi) Methods() []string {
// Execute given request // Execute given request
func (self *adminApi) Execute(req *shared.Request) (interface{}, error) { 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) return callback(self, req)
} }
@ -79,7 +80,7 @@ func (self *adminApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *adminApi) Name() string { func (self *adminApi) Name() string {
return AdminApiName return shared.AdminApiName
} }
func (self *adminApi) ApiVersion() string { func (self *adminApi) ApiVersion() string {
@ -88,7 +89,7 @@ func (self *adminApi) ApiVersion() string {
func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) { func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) {
args := new(AddPeerArgs) 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()) return nil, shared.NewDecodeParamError(err.Error())
} }
@ -103,33 +104,6 @@ func (self *adminApi) Peers(req *shared.Request) (interface{}, error) {
return self.ethereum.PeersInfo(), nil 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) { func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) {
return self.ethereum.NodeInfo(), nil 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) { func (self *adminApi) ImportChain(req *shared.Request) (interface{}, error) {
args := new(ImportExportChainArgs) 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()) 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) { func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) {
args := new(ImportExportChainArgs) 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()) 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) { func (self *adminApi) Verbosity(req *shared.Request) (interface{}, error) {
args := new(VerbosityArgs) 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()) 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() pending, cached, importing, estimate := self.ethereum.Downloader().Stats()
return map[string]interface{}{ return map[string]interface{}{
"blocksAvailable": pending, "blocksAvailable": pending,
"blocksWaitingForImport": cached, "blocksWaitingForImport": cached,
"importing": importing, "importing": importing,
"estimate": estimate.String(), "estimate": estimate.String(),
}, nil }, nil
} }
func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) { func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) {
args := new(SetSolcArgs) 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()) return nil, shared.NewDecodeParamError(err.Error())
} }
@ -241,3 +215,32 @@ func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) {
} }
return solc.Info(), nil 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
}

@ -95,3 +95,55 @@ func (args *SetSolcArgs) UnmarshalJSON(b []byte) (err error) {
return shared.NewInvalidTypeError("path", "not a string") 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
}

@ -39,6 +39,20 @@ web3._extend({
params: 1, params: 1,
inputFormatter: [web3._extend.utils.formatInputString], inputFormatter: [web3._extend.utils.formatInputString],
outputFormatter: web3._extend.formatters.formatOutputString 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: properties:

@ -1,48 +1,10 @@
package api package api
import ( import (
"strings"
"github.com/ethereum/go-ethereum/rpc/shared" "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 // Merge multiple API's to a single API instance
func Merge(apis ...EthereumApi) EthereumApi { func Merge(apis ...shared.EthereumApi) shared.EthereumApi {
return newMergedApi(apis...) return newMergedApi(apis...)
} }

@ -3,7 +3,14 @@ package api
import ( import (
"testing" "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/codec"
"github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/xeth"
) )
func TestParseApiString(t *testing.T) { 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(&eth.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))
}
}

@ -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
}

@ -1,4 +1,4 @@
package rpc package api
import ( import (
"bytes" "bytes"
@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/rpc/shared"
) )
func TestBlockheightInvalidString(t *testing.T) { func TestBlockheightInvalidString(t *testing.T) {
@ -68,7 +70,7 @@ func ExpectValidationError(err error) string {
switch err.(type) { switch err.(type) {
case nil: case nil:
str = "Expected error but didn't get one" str = "Expected error but didn't get one"
case *ValidationError: case *shared.ValidationError:
break break
default: default:
str = fmt.Sprintf("Expected *rpc.ValidationError but got %T with message `%s`", err, err.Error()) 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) { switch err.(type) {
case nil: case nil:
str = "Expected error but didn't get one" str = "Expected error but didn't get one"
case *InvalidTypeError: case *shared.InvalidTypeError:
break break
default: default:
str = fmt.Sprintf("Expected *rpc.InvalidTypeError but got %T with message `%s`", err, err.Error()) 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) { switch err.(type) {
case nil: case nil:
str = "Expected error but didn't get one" str = "Expected error but didn't get one"
case *InsufficientParamsError: case *shared.InsufficientParamsError:
break break
default: default:
str = fmt.Sprintf("Expected *rpc.InsufficientParamsError but got %T with message %s", err, err.Error()) 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) { switch err.(type) {
case nil: case nil:
str = "Expected error but didn't get one" str = "Expected error but didn't get one"
case *DecodeParamError: case *shared.DecodeParamError:
break break
default: default:
str = fmt.Sprintf("Expected *rpc.DecodeParamError but got %T with message `%s`", err, err.Error()) str = fmt.Sprintf("Expected *rpc.DecodeParamError but got %T with message `%s`", err, err.Error())

@ -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
}

@ -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
}

@ -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:
[
]
});
`

@ -71,7 +71,7 @@ func (self *debugApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *debugApi) Name() string { func (self *debugApi) Name() string {
return DebugApiName return shared.DebugApiName
} }
func (self *debugApi) ApiVersion() string { func (self *debugApi) ApiVersion() string {

@ -28,48 +28,49 @@ type ethhandler func(*ethApi, *shared.Request) (interface{}, error)
var ( var (
ethMapping = map[string]ethhandler{ ethMapping = map[string]ethhandler{
"eth_accounts": (*ethApi).Accounts, "eth_accounts": (*ethApi).Accounts,
"eth_blockNumber": (*ethApi).BlockNumber, "eth_blockNumber": (*ethApi).BlockNumber,
"eth_getBalance": (*ethApi).GetBalance, "eth_getBalance": (*ethApi).GetBalance,
"eth_protocolVersion": (*ethApi).ProtocolVersion, "eth_protocolVersion": (*ethApi).ProtocolVersion,
"eth_coinbase": (*ethApi).Coinbase, "eth_coinbase": (*ethApi).Coinbase,
"eth_mining": (*ethApi).IsMining, "eth_mining": (*ethApi).IsMining,
"eth_gasPrice": (*ethApi).GasPrice, "eth_gasPrice": (*ethApi).GasPrice,
"eth_getStorage": (*ethApi).GetStorage, "eth_getStorage": (*ethApi).GetStorage,
"eth_storageAt": (*ethApi).GetStorage, "eth_storageAt": (*ethApi).GetStorage,
"eth_getStorageAt": (*ethApi).GetStorageAt, "eth_getStorageAt": (*ethApi).GetStorageAt,
"eth_getTransactionCount": (*ethApi).GetTransactionCount, "eth_getTransactionCount": (*ethApi).GetTransactionCount,
"eth_getBlockTransactionCountByHash": (*ethApi).GetBlockTransactionCountByHash, "eth_getBlockTransactionCountByHash": (*ethApi).GetBlockTransactionCountByHash,
"eth_getBlockTransactionCountByNumber": (*ethApi).GetBlockTransactionCountByNumber, "eth_getBlockTransactionCountByNumber": (*ethApi).GetBlockTransactionCountByNumber,
"eth_getUncleCountByBlockHash": (*ethApi).GetUncleCountByBlockHash, "eth_getUncleCountByBlockHash": (*ethApi).GetUncleCountByBlockHash,
"eth_getUncleCountByBlockNumber": (*ethApi).GetUncleCountByBlockNumber, "eth_getUncleCountByBlockNumber": (*ethApi).GetUncleCountByBlockNumber,
"eth_getData": (*ethApi).GetData, "eth_getData": (*ethApi).GetData,
"eth_getCode": (*ethApi).GetData, "eth_getCode": (*ethApi).GetData,
"eth_sign": (*ethApi).Sign, "eth_sign": (*ethApi).Sign,
"eth_sendRawTransaction": (*ethApi).PushTx, "eth_sendRawTransaction": (*ethApi).SendRawTransaction,
"eth_sendTransaction": (*ethApi).SendTransaction, "eth_sendTransaction": (*ethApi).SendTransaction,
"eth_transact": (*ethApi).SendTransaction, "eth_transact": (*ethApi).SendTransaction,
"eth_estimateGas": (*ethApi).EstimateGas, "eth_estimateGas": (*ethApi).EstimateGas,
"eth_call": (*ethApi).Call, "eth_call": (*ethApi).Call,
"eth_flush": (*ethApi).Flush, "eth_flush": (*ethApi).Flush,
"eth_getBlockByHash": (*ethApi).GetBlockByHash, "eth_getBlockByHash": (*ethApi).GetBlockByHash,
"eth_getBlockByNumber": (*ethApi).GetBlockByNumber, "eth_getBlockByNumber": (*ethApi).GetBlockByNumber,
"eth_getTransactionByHash": (*ethApi).GetTransactionByHash, "eth_getTransactionByHash": (*ethApi).GetTransactionByHash,
"eth_getTransactionByBlockHashAndIndex": (*ethApi).GetTransactionByBlockHashAndIndex, "eth_getTransactionByBlockNumberAndIndex": (*ethApi).GetTransactionByBlockNumberAndIndex,
"eth_getUncleByBlockHashAndIndex": (*ethApi).GetUncleByBlockHashAndIndex, "eth_getTransactionByBlockHashAndIndex": (*ethApi).GetTransactionByBlockHashAndIndex,
"eth_getUncleByBlockNumberAndIndex": (*ethApi).GetUncleByBlockNumberAndIndex, "eth_getUncleByBlockHashAndIndex": (*ethApi).GetUncleByBlockHashAndIndex,
"eth_getCompilers": (*ethApi).GetCompilers, "eth_getUncleByBlockNumberAndIndex": (*ethApi).GetUncleByBlockNumberAndIndex,
"eth_compileSolidity": (*ethApi).CompileSolidity, "eth_getCompilers": (*ethApi).GetCompilers,
"eth_newFilter": (*ethApi).NewFilter, "eth_compileSolidity": (*ethApi).CompileSolidity,
"eth_newBlockFilter": (*ethApi).NewBlockFilter, "eth_newFilter": (*ethApi).NewFilter,
"eth_newPendingTransactionFilter": (*ethApi).NewPendingTransactionFilter, "eth_newBlockFilter": (*ethApi).NewBlockFilter,
"eth_uninstallFilter": (*ethApi).UninstallFilter, "eth_newPendingTransactionFilter": (*ethApi).NewPendingTransactionFilter,
"eth_getFilterChanges": (*ethApi).GetFilterChanges, "eth_uninstallFilter": (*ethApi).UninstallFilter,
"eth_getFilterLogs": (*ethApi).GetFilterLogs, "eth_getFilterChanges": (*ethApi).GetFilterChanges,
"eth_getLogs": (*ethApi).GetLogs, "eth_getFilterLogs": (*ethApi).GetFilterLogs,
"eth_hashrate": (*ethApi).Hashrate, "eth_getLogs": (*ethApi).GetLogs,
"eth_getWork": (*ethApi).GetWork, "eth_hashrate": (*ethApi).Hashrate,
"eth_submitWork": (*ethApi).SubmitWork, "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 { func (self *ethApi) Name() string {
return EthApiName return shared.EthApiName
} }
func (self *ethApi) ApiVersion() string { 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) { 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) { 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 return v, nil
} }
func (self *ethApi) SendRawTransaction(req *shared.Request) (interface{}, error) {
func (self *ethApi) PushTx(req *shared.Request) (interface{}, error) {
args := new(NewDataArgs) args := new(NewDataArgs)
if err := self.codec.Decode(req.Params, &args); err != nil { if err := self.codec.Decode(req.Params, &args); err != nil {
return nil, shared.NewDecodeParamError(err.Error()) return nil, shared.NewDecodeParamError(err.Error())

@ -227,32 +227,32 @@ func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) {
} }
type NewDataArgs struct { type NewDataArgs struct {
Data string Data string
} }
func (args *NewDataArgs) UnmarshalJSON(b []byte) (err error) { func (args *NewDataArgs) UnmarshalJSON(b []byte) (err error) {
var obj []interface{} var obj []interface{}
if err := json.Unmarshal(b, &obj); err != nil { if err := json.Unmarshal(b, &obj); err != nil {
return shared.NewDecodeParamError(err.Error()) return shared.NewDecodeParamError(err.Error())
} }
// Check for sufficient params // Check for sufficient params
if len(obj) < 1 { if len(obj) < 1 {
return shared.NewInsufficientParamsError(len(obj), 1) return shared.NewInsufficientParamsError(len(obj), 1)
} }
data, ok := obj[0].(string) data, ok := obj[0].(string)
if !ok { if !ok {
return shared.NewInvalidTypeError("data", "not a string") return shared.NewInvalidTypeError("data", "not a string")
} }
args.Data = data args.Data = data
if len(args.Data) == 0 { if len(args.Data) == 0 {
return shared.NewValidationError("data", "is required") return shared.NewValidationError("data", "is required")
} }
return nil return nil
} }
type NewSigArgs struct { type NewSigArgs struct {
@ -466,21 +466,21 @@ func (args *CallArgs) UnmarshalJSON(b []byte) (err error) {
} }
args.Value = num args.Value = num
if ext.Gas == nil { if ext.Gas != nil {
num = big.NewInt(0)
} else {
if num, err = numString(ext.Gas); err != nil { if num, err = numString(ext.Gas); err != nil {
return err return err
} }
} else {
num = nil
} }
args.Gas = num args.Gas = num
if ext.GasPrice == nil { if ext.GasPrice != nil {
num = big.NewInt(0)
} else {
if num, err = numString(ext.GasPrice); err != nil { if num, err = numString(ext.GasPrice); err != nil {
return err return err
} }
} else {
num = nil
} }
args.GasPrice = num args.GasPrice = num

@ -1,3 +1,20 @@
package api package api
// JS api provided by web3.js // 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
})
]
});
`

@ -1,6 +1,8 @@
package api package api
import ( import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
) )
@ -11,14 +13,14 @@ const (
// combines multiple API's // combines multiple API's
type MergedApi struct { type MergedApi struct {
apis map[string]string apis map[string]string
methods map[string]EthereumApi methods map[string]shared.EthereumApi
} }
// create new merged api instance // create new merged api instance
func newMergedApi(apis ...EthereumApi) *MergedApi { func newMergedApi(apis ...shared.EthereumApi) *MergedApi {
mergedApi := new(MergedApi) mergedApi := new(MergedApi)
mergedApi.apis = make(map[string]string, len(apis)) 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 { for _, api := range apis {
mergedApi.apis[api.Name()] = api.ApiVersion() 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 // Call the correct API's Execute method for the given request
func (self *MergedApi) Execute(req *shared.Request) (interface{}, error) { 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 { if res, _ := self.handle(req); res != nil {
return res, nil return res, nil
} }
@ -50,7 +54,7 @@ func (self *MergedApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *MergedApi) Name() string { func (self *MergedApi) Name() string {
return MergedApiName return shared.MergedApiName
} }
func (self *MergedApi) ApiVersion() string { func (self *MergedApi) ApiVersion() string {

@ -66,7 +66,7 @@ func (self *minerApi) Methods() []string {
} }
func (self *minerApi) Name() string { func (self *minerApi) Name() string {
return MinerApiName return shared.MinerApiName
} }
func (self *minerApi) ApiVersion() string { func (self *minerApi) ApiVersion() string {

@ -63,7 +63,7 @@ func (self *netApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *netApi) Name() string { func (self *netApi) Name() string {
return NetApiName return shared.NetApiName
} }
func (self *netApi) ApiVersion() string { func (self *netApi) ApiVersion() string {
@ -77,7 +77,7 @@ func (self *netApi) Version(req *shared.Request) (interface{}, error) {
// Number of connected peers // Number of connected peers
func (self *netApi) PeerCount(req *shared.Request) (interface{}, error) { 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) { func (self *netApi) IsListening(req *shared.Request) (interface{}, error) {

@ -66,7 +66,7 @@ func (self *personalApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *personalApi) Name() string { func (self *personalApi) Name() string {
return PersonalApiName return shared.PersonalApiName
} }
func (self *personalApi) ApiVersion() string { func (self *personalApi) ApiVersion() string {

@ -72,7 +72,7 @@ func (self *shhApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *shhApi) Name() string { func (self *shhApi) Name() string {
return ShhApiName return shared.ShhApiName
} }
func (self *shhApi) ApiVersion() string { func (self *shhApi) ApiVersion() string {

@ -60,7 +60,7 @@ func (self *txPoolApi) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *txPoolApi) Name() string { func (self *txPoolApi) Name() string {
return TxPoolApiName return shared.TxPoolApiName
} }
func (self *txPoolApi) ApiVersion() string { func (self *txPoolApi) ApiVersion() string {

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
) )
@ -23,6 +24,14 @@ var (
"chainSyncStatus", "chainSyncStatus",
"setSolc", "setSolc",
"datadir", "datadir",
"startRPC",
"stopRPC",
},
"db": []string{
"getString",
"putString",
"getHex",
"putHex",
}, },
"debug": []string{ "debug": []string{
"dumpBlock", "dumpBlock",
@ -123,33 +132,35 @@ var (
) )
// Parse a comma separated API string to individual api's // 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 { if len(strings.TrimSpace(apistr)) == 0 {
return nil, fmt.Errorf("Empty apistr provided") return nil, fmt.Errorf("Empty apistr provided")
} }
names := strings.Split(apistr, ",") names := strings.Split(apistr, ",")
apis := make([]EthereumApi, len(names)) apis := make([]shared.EthereumApi, len(names))
for i, name := range names { for i, name := range names {
switch strings.ToLower(strings.TrimSpace(name)) { switch strings.ToLower(strings.TrimSpace(name)) {
case AdminApiName: case shared.AdminApiName:
apis[i] = NewAdminApi(xeth, eth, codec) apis[i] = NewAdminApi(xeth, eth, codec)
case DebugApiName: case shared.DebugApiName:
apis[i] = NewDebugApi(xeth, eth, codec) 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) apis[i] = NewEthApi(xeth, codec)
case MinerApiName: case shared.MinerApiName:
apis[i] = NewMinerApi(eth, codec) apis[i] = NewMinerApi(eth, codec)
case NetApiName: case shared.NetApiName:
apis[i] = NewNetApi(xeth, eth, codec) apis[i] = NewNetApi(xeth, eth, codec)
case ShhApiName: case shared.ShhApiName:
apis[i] = NewShhApi(xeth, eth, codec) apis[i] = NewShhApi(xeth, eth, codec)
case TxPoolApiName: case shared.TxPoolApiName:
apis[i] = NewTxPoolApi(xeth, eth, codec) apis[i] = NewTxPoolApi(xeth, eth, codec)
case PersonalApiName: case shared.PersonalApiName:
apis[i] = NewPersonalApi(xeth, eth, codec) apis[i] = NewPersonalApi(xeth, eth, codec)
case Web3ApiName: case shared.Web3ApiName:
apis[i] = NewWeb3Api(xeth, codec) apis[i] = NewWeb3Api(xeth, codec)
default: default:
return nil, fmt.Errorf("Unknown API '%s'", name) 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 { func Javascript(name string) string {
switch strings.ToLower(strings.TrimSpace(name)) { switch strings.ToLower(strings.TrimSpace(name)) {
case AdminApiName: case shared.AdminApiName:
return Admin_JS return Admin_JS
case DebugApiName: case shared.DebugApiName:
return Debug_JS return Debug_JS
case MinerApiName: case shared.DbApiName:
return Db_JS
case shared.EthApiName:
return Eth_JS
case shared.MinerApiName:
return Miner_JS return Miner_JS
case NetApiName: case shared.NetApiName:
return Net_JS return Net_JS
case ShhApiName: case shared.ShhApiName:
return Shh_JS return Shh_JS
case TxPoolApiName: case shared.TxPoolApiName:
return TxPool_JS return TxPool_JS
case PersonalApiName: case shared.PersonalApiName:
return Personal_JS return Personal_JS
} }

@ -60,7 +60,7 @@ func (self *web3Api) Execute(req *shared.Request) (interface{}, error) {
} }
func (self *web3Api) Name() string { func (self *web3Api) Name() string {
return Web3ApiName return shared.Web3ApiName
} }
func (self *web3Api) ApiVersion() string { func (self *web3Api) ApiVersion() string {

@ -1,5 +1,29 @@
package api package api
import (
"encoding/json"
"github.com/ethereum/go-ethereum/rpc/shared"
)
type Sha3Args struct { type Sha3Args struct {
Data string 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
}

@ -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(&eth.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")
// }
// }

File diff suppressed because it is too large Load Diff

@ -1,7 +1,111 @@
package comms 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 { type EthereumClient interface {
// Close underlaying connection
Close() Close()
// Send request
Send(interface{}) error Send(interface{}) error
// Receive response
Recv() (interface{}, error) Recv() (interface{}, error)
// List with modules this client supports
SupportedModules() (map[string]string, error)
}
func handle(conn net.Conn, api 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")
} }

@ -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
}

@ -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)
})
}

@ -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")
}

@ -1,8 +1,13 @@
package comms package comms
import ( 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/codec"
"github.com/ethereum/go-ethereum/rpc/shared"
) )
type IpcConfig struct { type IpcConfig struct {
@ -10,19 +15,74 @@ type IpcConfig struct {
} }
type ipcClient struct { type ipcClient struct {
c codec.ApiCoder endpoint string
codec codec.Codec
coder codec.ApiCoder
} }
func (self *ipcClient) Close() { func (self *ipcClient) Close() {
self.c.Close() self.coder.Close()
} }
func (self *ipcClient) Send(req interface{}) error { 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) { 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 // 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 // Start IPC server
func StartIpc(cfg IpcConfig, codec codec.Codec, apis ...api.EthereumApi) error { func StartIpc(cfg IpcConfig, codec codec.Codec, offeredApi shared.EthereumApi) error {
offeredApi := api.Merge(apis...)
return startIpc(cfg, codec, offeredApi) return startIpc(cfg, codec, offeredApi)
} }

@ -3,13 +3,11 @@
package comms package comms
import ( import (
"io"
"net" "net"
"os" "os"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
) )
@ -20,10 +18,20 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
return nil, err return nil, err
} }
return &ipcClient{codec.New(c)}, nil return &ipcClient{cfg.Endpoint, codec, codec.New(c)}, nil
} }
func 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 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"}) 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 continue
} }
go func(conn net.Conn) { go handle(conn, api, codec)
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)
} }
os.Remove(cfg.Endpoint) os.Remove(cfg.Endpoint)

@ -14,7 +14,6 @@ import (
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
) )
@ -641,10 +640,18 @@ func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
return nil, err return nil, err
} }
return &ipcClient{codec.New(c)}, nil return &ipcClient{cfg.Endpoint, codec, codec.New(c)}, nil
} }
func 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 os.Remove(cfg.Endpoint) // in case it still exists from a previous run
l, err := Listen(cfg.Endpoint) l, err := Listen(cfg.Endpoint)

@ -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)
}

@ -2,30 +2,26 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt"
"reflect"
"github.com/ethereum/go-ethereum/jsre" "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/comms"
"github.com/ethereum/go-ethereum/rpc/shared" "github.com/ethereum/go-ethereum/rpc/shared"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
) )
type Jeth struct { type Jeth struct {
ethApi *EthereumApi ethApi shared.EthereumApi
re *jsre.JSRE re *jsre.JSRE
ipcpath string client comms.EthereumClient
} }
func NewJeth(ethApi *EthereumApi, re *jsre.JSRE, ipcpath string) *Jeth { func NewJeth(ethApi shared.EthereumApi, re *jsre.JSRE, client comms.EthereumClient) *Jeth {
return &Jeth{ethApi, re, ipcpath} return &Jeth{ethApi, re, client}
} }
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
rpcerr := &RpcErrorObject{code, msg} rpcerr := &shared.ErrorObject{code, msg}
call.Otto.Set("ret_jsonrpc", jsonrpcver) call.Otto.Set("ret_jsonrpc", shared.JsonRpcVersion)
call.Otto.Set("ret_id", id) call.Otto.Set("ret_id", id)
call.Otto.Set("ret_error", rpcerr) call.Otto.Set("ret_error", rpcerr)
response, _ = call.Otto.Run(` response, _ = call.Otto.Run(`
@ -41,11 +37,11 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
} }
jsonreq, err := json.Marshal(reqif) jsonreq, err := json.Marshal(reqif)
var reqs []RpcRequest var reqs []shared.Request
batch := true batch := true
err = json.Unmarshal(jsonreq, &reqs) err = json.Unmarshal(jsonreq, &reqs)
if err != nil { if err != nil {
reqs = make([]RpcRequest, 1) reqs = make([]shared.Request, 1)
err = json.Unmarshal(jsonreq, &reqs[0]) err = json.Unmarshal(jsonreq, &reqs[0])
batch = false batch = false
} }
@ -55,12 +51,16 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
for i, req := range reqs { for i, req := range reqs {
var respif interface{} 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 { if err != nil {
fmt.Println("Error response:", err)
return self.err(call, -32603, err.Error(), req.Id) 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) call.Otto.Set("ret_id", req.Id)
res, _ := json.Marshal(respif) res, _ := json.Marshal(respif)
@ -87,85 +87,3 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
return 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
}

@ -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
}

@ -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
}

@ -7,6 +7,21 @@ import (
"github.com/ethereum/go-ethereum/logger/glog" "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 // RPC request
type Request struct { type Request struct {
Id interface{} `json:"id"` Id interface{} `json:"id"`
@ -42,6 +57,18 @@ type ErrorObject struct {
// Data interface{} `json:"data"` // 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{} { func NewRpcResponse(id interface{}, jsonrpcver string, reply interface{}, err error) *interface{} {
var response interface{} var response interface{}

@ -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,
}, ",")
)

@ -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)
}
}

@ -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)
}
}
Loading…
Cancel
Save