ソースを参照

Merge pull request #1239 from bas-vk/rpc-apis

RPC refactoring
Jeffrey Wilcke 10 年 前
コミット
e2c2d8e15e

+ 5 - 0
Makefile

@@ -10,6 +10,11 @@ geth:
 	@echo "Done building."
 	@echo "Run \"$(GOBIN)/geth\" to launch geth."
 
+console:
+	build/env.sh go install -v $(shell build/ldflags.sh) ./cmd/console
+	@echo "Done building."
+	@echo "Run \"$(GOBIN)/console\" to launch the console."
+
 mist:
 	build/env.sh go install -v $(shell build/ldflags.sh) ./cmd/mist
 	@echo "Done building."

+ 9 - 0
cmd/console/admin.go

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

ファイルの差分が大きいため隠しています
+ 3 - 0
cmd/console/contracts.go


+ 431 - 0
cmd/console/js.go

@@ -0,0 +1,431 @@
+// 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/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) interactive() {
+	// 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)
+	}
+}

+ 101 - 0
cmd/console/main.go

@@ -0,0 +1,101 @@
+/*
+	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 ether console")
+)
+
+func init() {
+	if gitCommit == "" {
+		nodeNameVersion = Version
+	} else {
+		nodeNameVersion = Version + "-" + gitCommit[:8]
+	}
+
+	app.Action = run
+	app.Flags = []cli.Flag{
+		utils.IPCDisabledFlag,
+		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 := ctx.GlobalString(utils.IPCPathFlag.Name)
+
+	repl := newJSRE(jspath, ipcpath)
+	repl.welcome(ipcpath)
+	repl.interactive()
+}

+ 6 - 5
cmd/geth/js.go

@@ -73,7 +73,7 @@ type jsre struct {
 	prompter
 }
 
-func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, interactive bool, f xeth.Frontend) *jsre {
+func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain, ipcpath string, interactive bool, f xeth.Frontend) *jsre {
 	js := &jsre{ethereum: ethereum, ps1: "> "}
 	// set default cors domain used by startRpc from CLI flag
 	js.corsDomain = corsDomain
@@ -84,7 +84,7 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, interactive boo
 	js.wait = js.xeth.UpdateState()
 	// update state in separare forever blocks
 	js.re = re.New(libPath)
-	js.apiBindings(f)
+	js.apiBindings(ipcpath, f)
 	js.adminBindings()
 
 	if !liner.TerminalSupported() || !interactive {
@@ -103,14 +103,15 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, interactive boo
 	return js
 }
 
-func (js *jsre) apiBindings(f xeth.Frontend) {
+func (js *jsre) apiBindings(ipcpath string, f xeth.Frontend) {
 	xe := xeth.New(js.ethereum, f)
 	ethApi := rpc.NewEthereumApi(xe)
-	jeth := rpc.NewJeth(ethApi, js.re)
+	jeth := rpc.NewJeth(ethApi, js.re, ipcpath)
 
 	js.re.Set("jeth", struct{}{})
 	t, _ := js.re.Get("jeth")
 	jethObj := t.Object()
+
 	jethObj.Set("send", jeth.Send)
 	jethObj.Set("sendAsync", jeth.Send)
 
@@ -119,7 +120,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) {
 		utils.Fatalf("Error loading bignumber.js: %v", err)
 	}
 
-	err = js.re.Compile("ethereum.js", re.Ethereum_JS)
+	err = js.re.Compile("ethereum.js", re.Web3_JS)
 	if err != nil {
 		utils.Fatalf("Error loading ethereum.js: %v", err)
 	}

+ 1 - 1
cmd/geth/js_test.go

@@ -105,7 +105,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
 		t.Errorf("Error creating DocServer: %v", err)
 	}
 	tf := &testjethre{ds: ds, stateDb: ethereum.ChainManager().State().Copy()}
-	repl := newJSRE(ethereum, assetPath, "", false, tf)
+	repl := newJSRE(ethereum, assetPath, "", "", false, tf)
 	tf.jsre = repl
 	return tmp, tf, ethereum
 }

+ 10 - 0
cmd/geth/main.go

@@ -239,6 +239,9 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
 		utils.RPCEnabledFlag,
 		utils.RPCListenAddrFlag,
 		utils.RPCPortFlag,
+		utils.IPCDisabledFlag,
+		utils.IPCApiFlag,
+		utils.IPCPathFlag,
 		utils.WhisperEnabledFlag,
 		utils.VMDebugFlag,
 		utils.ProtocolVersionFlag,
@@ -305,6 +308,7 @@ func console(ctx *cli.Context) {
 		ethereum,
 		ctx.String(utils.JSpathFlag.Name),
 		ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
+		ctx.GlobalString(utils.IPCPathFlag.Name),
 		true,
 		nil,
 	)
@@ -326,6 +330,7 @@ func execJSFiles(ctx *cli.Context) {
 		ethereum,
 		ctx.String(utils.JSpathFlag.Name),
 		ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
+		ctx.GlobalString(utils.IPCPathFlag.Name),
 		false,
 		nil,
 	)
@@ -382,6 +387,11 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
 		}
 	}
 	// Start auxiliary services if enabled.
+	if !ctx.GlobalBool(utils.IPCDisabledFlag.Name) {
+		if err := utils.StartIPC(eth, ctx); err != nil {
+			utils.Fatalf("Error string IPC: %v", err)
+		}
+	}
 	if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
 		if err := utils.StartRPC(eth, ctx); err != nil {
 			utils.Fatalf("Error starting RPC: %v", err)

+ 33 - 0
cmd/utils/flags.go

@@ -24,6 +24,9 @@ import (
 	"github.com/ethereum/go-ethereum/p2p/nat"
 	"github.com/ethereum/go-ethereum/rpc"
 	"github.com/ethereum/go-ethereum/xeth"
+	"github.com/ethereum/go-ethereum/rpc/api"
+	"github.com/ethereum/go-ethereum/rpc/comms"
+	"github.com/ethereum/go-ethereum/rpc/codec"
 )
 
 func init() {
@@ -206,6 +209,20 @@ var (
 		Usage: "Domain on which to send Access-Control-Allow-Origin header",
 		Value: "",
 	}
+	IPCDisabledFlag = cli.BoolFlag{
+		Name:  "ipcdisable",
+		Usage: "Disable the IPC-RPC server",
+	}
+	IPCApiFlag = cli.StringFlag{
+		Name:  "ipcapi",
+		Usage: "Specify the API's which are offered over this interface",
+		Value: api.DefaultIpcApis,
+	}
+	IPCPathFlag = DirectoryFlag{
+		Name:  "ipcpath",
+		Usage: "Filename for IPC socket/pipe",
+		Value: DirectoryString{common.DefaultIpcPath()},
+	}
 	// Network Settings
 	MaxPeersFlag = cli.IntFlag{
 		Name:  "maxpeers",
@@ -368,6 +385,22 @@ func MakeAccountManager(ctx *cli.Context) *accounts.Manager {
 	return accounts.NewManager(ks)
 }
 
+func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error {
+	config := comms.IpcConfig{
+		Endpoint: ctx.GlobalString(IPCPathFlag.Name),
+	}
+
+	xeth := xeth.New(eth, nil)
+	codec := codec.JSON
+
+	apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec, xeth, eth)
+	if err != nil {
+		return err
+	}
+
+	return comms.StartIpc(config, codec, apis...)
+}
+
 func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error {
 	config := rpc.RpcConfig{
 		ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name),

+ 4 - 0
common/path.go

@@ -94,6 +94,10 @@ func DefaultDataDir() string {
 	}
 }
 
+func DefaultIpcPath() string {
+	return filepath.Join(DefaultDataDir(), "geth.ipc")
+}
+
 func IsWindows() bool {
 	return runtime.GOOS == "windows"
 }

ファイルの差分が大きいため隠しています
+ 294 - 168
jsre/ethereum_js.go


+ 243 - 0
rpc/api/admin.go

@@ -0,0 +1,243 @@
+package api
+
+import (
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+	"github.com/ethereum/go-ethereum/xeth"
+)
+
+const (
+	AdminApiversion = "1.0"
+	importBatchSize = 2500
+)
+
+var (
+	// mapping between methods and handlers
+	AdminMapping = map[string]adminhandler{
+		//		"admin_startRPC": (*adminApi).StartRPC,
+		//		"admin_stopRPC":  (*adminApi).StopRPC,
+		"admin_addPeer":         (*adminApi).AddPeer,
+		"admin_peers":           (*adminApi).Peers,
+		"admin_nodeInfo":        (*adminApi).NodeInfo,
+		"admin_exportChain":     (*adminApi).ExportChain,
+		"admin_importChain":     (*adminApi).ImportChain,
+		"admin_verbosity":       (*adminApi).Verbosity,
+		"admin_chainSyncStatus": (*adminApi).ChainSyncStatus,
+		"admin_setSolc":         (*adminApi).SetSolc,
+		"admin_datadir":         (*adminApi).DataDir,
+	}
+)
+
+// admin callback handler
+type adminhandler func(*adminApi, *shared.Request) (interface{}, error)
+
+// admin api provider
+type adminApi struct {
+	xeth     *xeth.XEth
+	ethereum *eth.Ethereum
+	methods  map[string]adminhandler
+	codec    codec.ApiCoder
+}
+
+// create a new admin api instance
+func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, coder codec.Codec) *adminApi {
+	return &adminApi{
+		xeth:     xeth,
+		ethereum: ethereum,
+		methods:  AdminMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *adminApi) 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 *adminApi) 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 *adminApi) Name() string {
+	return AdminApiName
+}
+
+func (self *adminApi) ApiVersion() string {
+	return AdminApiversion
+}
+
+func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) {
+	args := new(AddPeerArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	err := self.ethereum.AddPeer(args.Url)
+	if err == nil {
+		return true, nil
+	}
+	return false, err
+}
+
+func (self *adminApi) Peers(req *shared.Request) (interface{}, error) {
+	return self.ethereum.PeersInfo(), nil
+}
+
+func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) {
+	return false, nil
+	//	Enable when http rpc interface is refactored to prevent import cycles
+	//	args := new(StartRpcArgs)
+	//	if err := self.codec.Decode(req.Params, &args); err != nil {
+	//		return nil, shared.NewDecodeParamError(err.Error())
+	//	}
+	//
+	//	cfg := rpc.RpcConfig{
+	//		ListenAddress: args.Address,
+	//		ListenPort:    args.Port,
+	//	}
+	//
+	//	err := rpc.Start(self.xeth, cfg)
+	//	if err == nil {
+	//		return true, nil
+	//	}
+	//	return false, err
+}
+
+func (self *adminApi) StopRPC(req *shared.Request) (interface{}, error) {
+	return false, nil
+	//	Enable when http rpc interface is refactored to prevent import cycles
+	//	rpc.Stop()
+	//	return true, nil
+}
+
+func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) {
+	return self.ethereum.NodeInfo(), nil
+}
+
+func (self *adminApi) DataDir(req *shared.Request) (interface{}, error) {
+	return self.ethereum.DataDir, nil
+}
+
+func hasAllBlocks(chain *core.ChainManager, bs []*types.Block) bool {
+	for _, b := range bs {
+		if !chain.HasBlock(b.Hash()) {
+			return false
+		}
+	}
+	return true
+}
+
+func (self *adminApi) ImportChain(req *shared.Request) (interface{}, error) {
+	args := new(ImportExportChainArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	fh, err := os.Open(args.Filename)
+	if err != nil {
+		return false, err
+	}
+	defer fh.Close()
+	stream := rlp.NewStream(fh, 0)
+
+	// Run actual the import.
+	blocks := make(types.Blocks, importBatchSize)
+	n := 0
+	for batch := 0; ; batch++ {
+
+		i := 0
+		for ; i < importBatchSize; i++ {
+			var b types.Block
+			if err := stream.Decode(&b); err == io.EOF {
+				break
+			} else if err != nil {
+				return false, fmt.Errorf("at block %d: %v", n, err)
+			}
+			blocks[i] = &b
+			n++
+		}
+		if i == 0 {
+			break
+		}
+		// Import the batch.
+		if hasAllBlocks(self.ethereum.ChainManager(), blocks[:i]) {
+			continue
+		}
+		if _, err := self.ethereum.ChainManager().InsertChain(blocks[:i]); err != nil {
+			return false, fmt.Errorf("invalid block %d: %v", n, err)
+		}
+	}
+	return true, nil
+}
+
+func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) {
+	args := new(ImportExportChainArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	fh, err := os.OpenFile(args.Filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
+	if err != nil {
+		return false, err
+	}
+	defer fh.Close()
+	if err := self.ethereum.ChainManager().Export(fh); err != nil {
+		return false, err
+	}
+
+	return true, nil
+}
+
+func (self *adminApi) Verbosity(req *shared.Request) (interface{}, error) {
+	args := new(VerbosityArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	glog.SetV(args.Level)
+	return true, nil
+}
+
+func (self *adminApi) ChainSyncStatus(req *shared.Request) (interface{}, error) {
+	pending, cached, importing, estimate := self.ethereum.Downloader().Stats()
+
+	return map[string]interface{}{
+		"blocksAvailable": pending,
+		"blocksWaitingForImport": cached,
+		"importing": importing,
+		"estimate": estimate.String(),
+	}, nil
+}
+
+func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) {
+	args := new(SetSolcArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	solc, err := self.xeth.SetSolc(args.Path)
+	if err != nil {
+		return nil, err
+	}
+	return solc.Info(), nil
+}

+ 97 - 0
rpc/api/admin_args.go

@@ -0,0 +1,97 @@
+package api
+
+import (
+	"encoding/json"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+type AddPeerArgs struct {
+	Url string
+}
+
+func (args *AddPeerArgs) 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.NewDecodeParamError("Expected enode as argument")
+	}
+
+	urlstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("url", "not a string")
+	}
+	args.Url = urlstr
+
+	return nil
+}
+
+type ImportExportChainArgs struct {
+	Filename string
+}
+
+func (args *ImportExportChainArgs) 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.NewDecodeParamError("Expected filename as argument")
+	}
+
+	filename, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("filename", "not a string")
+	}
+	args.Filename = filename
+
+	return nil
+}
+
+type VerbosityArgs struct {
+	Level int
+}
+
+func (args *VerbosityArgs) 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.NewDecodeParamError("Expected enode as argument")
+	}
+
+	level, err := numString(obj[0])
+	if err == nil {
+		args.Level = int(level.Int64())
+	}
+
+	return nil
+}
+
+type SetSolcArgs struct {
+	Path string
+}
+
+func (args *SetSolcArgs) 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.NewDecodeParamError("Expected path as argument")
+	}
+
+	if pathstr, ok := obj[0].(string); ok {
+		args.Path = pathstr
+		return nil
+	}
+
+	return shared.NewInvalidTypeError("path", "not a string")
+}

+ 68 - 0
rpc/api/admin_js.go

@@ -0,0 +1,68 @@
+package api
+
+const Admin_JS = `
+web3._extend({
+	property: 'admin',
+	methods:
+	[
+		new web3._extend.Method({
+			name: 'addPeer',
+			call: 'admin_addPeer',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'exportChain',
+			call: 'admin_exportChain',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: function(obj) { return obj; }
+		}),
+		new web3._extend.Method({
+			name: 'importChain',
+			call: 'admin_importChain',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: function(obj) { return obj; }
+		}),
+		new web3._extend.Method({
+			name: 'verbosity',
+			call: 'admin_verbosity',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'setSolc',
+			call: 'admin_setSolc',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: web3._extend.formatters.formatOutputString
+		})
+	],
+	properties:
+	[
+		new web3._extend.Property({
+			name: 'nodeInfo',
+			getter: 'admin_nodeInfo',
+			outputFormatter: web3._extend.formatters.formatOutputString
+		}),
+		new web3._extend.Property({
+			name: 'peers',
+			getter: 'admin_peers',
+			outputFormatter: function(obj) { return obj; }
+		}),
+		new web3._extend.Property({
+			name: 'datadir',
+			getter: 'admin_datadir',
+			outputFormatter: web3._extend.formatters.formatOutputString
+		}),
+		new web3._extend.Property({
+			name: 'chainSyncStatus',
+			getter: 'admin_chainSyncStatus',
+			outputFormatter: function(obj) { return obj; }
+		})
+	]
+});
+`

+ 48 - 0
rpc/api/api.go

@@ -0,0 +1,48 @@
+package api
+
+import (
+	"strings"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+const (
+	AdminApiName    = "admin"
+	EthApiName      = "eth"
+	DebugApiName    = "debug"
+	MergedApiName   = "merged"
+	MinerApiName    = "miner"
+	NetApiName      = "net"
+	ShhApiName      = "shh"
+	TxPoolApiName   = "txpool"
+	PersonalApiName = "personal"
+	Web3ApiName     = "web3"
+)
+
+var (
+	// List with all API's which are offered over the IPC interface by default
+	DefaultIpcApis = strings.Join([]string{
+		AdminApiName, EthApiName, DebugApiName, MinerApiName, NetApiName,
+		ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName,
+	}, ",")
+)
+
+// Ethereum RPC API interface
+type EthereumApi interface {
+	// API identifier
+	Name() string
+
+	// API version
+	ApiVersion() string
+
+	// Execute the given request and returns the response or an error
+	Execute(*shared.Request) (interface{}, error)
+
+	// List of supported RCP methods this API provides
+	Methods() []string
+}
+
+// Merge multiple API's to a single API instance
+func Merge(apis ...EthereumApi) EthereumApi {
+	return newMergedApi(apis...)
+}

+ 42 - 0
rpc/api/api_test.go

@@ -0,0 +1,42 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/ethereum/go-ethereum/rpc/codec"
+)
+
+func TestParseApiString(t *testing.T) {
+	apis, err := ParseApiString("", codec.JSON, nil, nil)
+	if err == nil {
+		t.Errorf("Expected an err from parsing empty API string but got nil")
+	}
+
+	if len(apis) != 0 {
+		t.Errorf("Expected 0 apis from empty API string")
+	}
+
+	apis, err = ParseApiString("eth", codec.JSON, nil, nil)
+	if err != nil {
+		t.Errorf("Expected nil err from parsing empty API string but got %v", err)
+	}
+
+	if len(apis) != 1 {
+		t.Errorf("Expected 1 apis but got %d - %v", apis, apis)
+	}
+
+	apis, err = ParseApiString("eth,eth", codec.JSON, nil, nil)
+	if err != nil {
+		t.Errorf("Expected nil err from parsing empty API string but got \"%v\"", err)
+	}
+
+	if len(apis) != 2 {
+		t.Errorf("Expected 2 apis but got %d - %v", apis, apis)
+	}
+
+	apis, err = ParseApiString("eth,invalid", codec.JSON, nil, nil)
+	if err == nil {
+		t.Errorf("Expected an err but got no err")
+	}
+
+}

+ 173 - 0
rpc/api/debug.go

@@ -0,0 +1,173 @@
+package api
+
+import (
+	"fmt"
+
+	"github.com/ethereum/ethash"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+	"github.com/ethereum/go-ethereum/xeth"
+)
+
+const (
+	DebugApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	DebugMapping = map[string]debughandler{
+		"debug_dumpBlock":    (*debugApi).DumpBlock,
+		"debug_getBlockRlp":  (*debugApi).GetBlockRlp,
+		"debug_printBlock":   (*debugApi).PrintBlock,
+		"debug_processBlock": (*debugApi).ProcessBlock,
+		"debug_seedHash":     (*debugApi).SeedHash,
+		"debug_setHead":      (*debugApi).SetHead,
+	}
+)
+
+// debug callback handler
+type debughandler func(*debugApi, *shared.Request) (interface{}, error)
+
+// admin api provider
+type debugApi struct {
+	xeth     *xeth.XEth
+	ethereum *eth.Ethereum
+	methods  map[string]debughandler
+	codec    codec.ApiCoder
+}
+
+// create a new debug api instance
+func NewDebugApi(xeth *xeth.XEth, ethereum *eth.Ethereum, coder codec.Codec) *debugApi {
+	return &debugApi{
+		xeth:     xeth,
+		ethereum: ethereum,
+		methods:  DebugMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *debugApi) 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 *debugApi) 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 *debugApi) Name() string {
+	return DebugApiName
+}
+
+func (self *debugApi) ApiVersion() string {
+	return DebugApiVersion
+}
+
+func (self *debugApi) PrintBlock(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	return fmt.Sprintf("%s", block), nil
+}
+
+func (self *debugApi) DumpBlock(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	if block == nil {
+		return nil, fmt.Errorf("block #%d not found", args.BlockNumber)
+	}
+
+	stateDb := state.New(block.Root(), self.ethereum.StateDb())
+	if stateDb == nil {
+		return nil, nil
+	}
+
+	return stateDb.RawDump(), nil
+}
+
+func (self *debugApi) GetBlockRlp(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	if block == nil {
+		return nil, fmt.Errorf("block #%d not found", args.BlockNumber)
+	}
+	encoded, err := rlp.EncodeToBytes(block)
+	return fmt.Sprintf("%x", encoded), err
+}
+
+func (self *debugApi) SetHead(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	if block == nil {
+		return nil, fmt.Errorf("block #%d not found", args.BlockNumber)
+	}
+
+	self.ethereum.ChainManager().SetHead(block)
+
+	return nil, nil
+}
+
+func (self *debugApi) ProcessBlock(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	if block == nil {
+		return nil, fmt.Errorf("block #%d not found", args.BlockNumber)
+	}
+
+	old := vm.Debug
+	defer func() { vm.Debug = old }()
+	vm.Debug = true
+
+	_, err := self.ethereum.BlockProcessor().RetryProcess(block)
+	if err == nil {
+		return true, nil
+	}
+	return false, err
+}
+
+func (self *debugApi) SeedHash(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	if hash, err := ethash.GetSeedHash(uint64(args.BlockNumber)); err == nil {
+		return fmt.Sprintf("0x%x", hash), nil
+	} else {
+		return nil, err
+	}
+}

+ 47 - 0
rpc/api/debug_args.go

@@ -0,0 +1,47 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+type WaitForBlockArgs struct {
+	MinHeight int
+	Timeout   int // in seconds
+}
+
+func (args *WaitForBlockArgs) 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 fmt.Errorf("waitForArgs needs 0, 1, 2 arguments")
+	}
+
+	// default values when not provided
+	args.MinHeight = -1
+	args.Timeout = -1
+
+	if len(obj) >= 1 {
+		var minHeight *big.Int
+		if minHeight, err = numString(obj[0]); err != nil {
+			return err
+		}
+		args.MinHeight = int(minHeight.Int64())
+	}
+
+	if len(obj) >= 2 {
+		timeout, err := numString(obj[1])
+		if err != nil {
+			return err
+		}
+		args.Timeout = int(timeout.Int64())
+	}
+
+	return nil
+}

+ 55 - 0
rpc/api/debug_js.go

@@ -0,0 +1,55 @@
+package api
+
+const Debug_JS = `
+web3._extend({
+	property: 'debug',
+	methods:
+	[
+		new web3._extend.Method({
+			name: 'printBlock',
+			call: 'debug_printBlock',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputString
+		}),
+		new web3._extend.Method({
+			name: 'getBlockRlp',
+			call: 'debug_getBlockRlp',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputString
+		}),
+		new web3._extend.Method({
+			name: 'setHead',
+			call: 'debug_setHead',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'processBlock',
+			call: 'debug_processBlock',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: function(obj) { return obj; }
+		}),
+		new web3._extend.Method({
+			name: 'seedHash',
+			call: 'debug_seedHash',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputString
+		})		,
+		new web3._extend.Method({
+			name: 'dumpBlock',
+			call: 'debug_dumpBlock',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: function(obj) { return obj; }
+		})
+	],
+	properties:
+	[
+	]
+});
+`

+ 535 - 0
rpc/api/eth.go

@@ -0,0 +1,535 @@
+package api
+
+import (
+	"bytes"
+	"encoding/json"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+	"github.com/ethereum/go-ethereum/xeth"
+)
+
+const (
+	EthApiVersion = "1.0"
+)
+
+// eth api provider
+// See https://github.com/ethereum/wiki/wiki/JSON-RPC
+type ethApi struct {
+	xeth    *xeth.XEth
+	methods map[string]ethhandler
+	codec   codec.ApiCoder
+}
+
+// eth callback handler
+type ethhandler func(*ethApi, *shared.Request) (interface{}, error)
+
+var (
+	ethMapping = map[string]ethhandler{
+		"eth_accounts":                          (*ethApi).Accounts,
+		"eth_blockNumber":                       (*ethApi).BlockNumber,
+		"eth_getBalance":                        (*ethApi).GetBalance,
+		"eth_protocolVersion":                   (*ethApi).ProtocolVersion,
+		"eth_coinbase":                          (*ethApi).Coinbase,
+		"eth_mining":                            (*ethApi).IsMining,
+		"eth_gasPrice":                          (*ethApi).GasPrice,
+		"eth_getStorage":                        (*ethApi).GetStorage,
+		"eth_storageAt":                         (*ethApi).GetStorage,
+		"eth_getStorageAt":                      (*ethApi).GetStorageAt,
+		"eth_getTransactionCount":               (*ethApi).GetTransactionCount,
+		"eth_getBlockTransactionCountByHash":    (*ethApi).GetBlockTransactionCountByHash,
+		"eth_getBlockTransactionCountByNumber":  (*ethApi).GetBlockTransactionCountByNumber,
+		"eth_getUncleCountByBlockHash":          (*ethApi).GetUncleCountByBlockHash,
+		"eth_getUncleCountByBlockNumber":        (*ethApi).GetUncleCountByBlockNumber,
+		"eth_getData":                           (*ethApi).GetData,
+		"eth_getCode":                           (*ethApi).GetData,
+		"eth_sign":                              (*ethApi).Sign,
+		"eth_sendTransaction":                   (*ethApi).SendTransaction,
+		"eth_transact":                          (*ethApi).SendTransaction,
+		"eth_estimateGas":                       (*ethApi).EstimateGas,
+		"eth_call":                              (*ethApi).Call,
+		"eth_flush":                             (*ethApi).Flush,
+		"eth_getBlockByHash":                    (*ethApi).GetBlockByHash,
+		"eth_getBlockByNumber":                  (*ethApi).GetBlockByNumber,
+		"eth_getTransactionByHash":              (*ethApi).GetTransactionByHash,
+		"eth_getTransactionByBlockHashAndIndex": (*ethApi).GetTransactionByBlockHashAndIndex,
+		"eth_getUncleByBlockHashAndIndex":       (*ethApi).GetUncleByBlockHashAndIndex,
+		"eth_getUncleByBlockNumberAndIndex":     (*ethApi).GetUncleByBlockNumberAndIndex,
+		"eth_getCompilers":                      (*ethApi).GetCompilers,
+		"eth_compileSolidity":                   (*ethApi).CompileSolidity,
+		"eth_newFilter":                         (*ethApi).NewFilter,
+		"eth_newBlockFilter":                    (*ethApi).NewBlockFilter,
+		"eth_newPendingTransactionFilter":       (*ethApi).NewPendingTransactionFilter,
+		"eth_uninstallFilter":                   (*ethApi).UninstallFilter,
+		"eth_getFilterChanges":                  (*ethApi).GetFilterChanges,
+		"eth_getFilterLogs":                     (*ethApi).GetFilterLogs,
+		"eth_getLogs":                           (*ethApi).GetLogs,
+		"eth_hashrate":                          (*ethApi).Hashrate,
+		"eth_getWork":                           (*ethApi).GetWork,
+		"eth_submitWork":                        (*ethApi).SubmitWork,
+	}
+)
+
+// create new ethApi instance
+func NewEthApi(xeth *xeth.XEth, codec codec.Codec) *ethApi {
+	return &ethApi{xeth, ethMapping, codec.New(nil)}
+}
+
+// collection with supported methods
+func (self *ethApi) 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 *ethApi) Execute(req *shared.Request) (interface{}, error) {
+	if callback, ok := self.methods[req.Method]; ok {
+		return callback(self, req)
+	}
+
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *ethApi) Name() string {
+	return EthApiName
+}
+
+func (self *ethApi) ApiVersion() string {
+	return EthApiVersion
+}
+
+func (self *ethApi) Accounts(req *shared.Request) (interface{}, error) {
+	return self.xeth.Accounts(), nil
+}
+
+func (self *ethApi) Hashrate(req *shared.Request) (interface{}, error) {
+	return newHexNum(self.xeth.HashRate()), nil
+}
+
+func (self *ethApi) BlockNumber(req *shared.Request) (interface{}, error) {
+	return self.xeth.CurrentBlock().Number(), nil
+}
+
+func (self *ethApi) GetBalance(req *shared.Request) (interface{}, error) {
+	args := new(GetBalanceArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	return self.xeth.AtStateNum(args.BlockNumber).BalanceAt(args.Address), nil
+}
+
+func (self *ethApi) ProtocolVersion(req *shared.Request) (interface{}, error) {
+	return self.xeth.EthVersion(), nil
+}
+
+func (self *ethApi) Coinbase(req *shared.Request) (interface{}, error) {
+	return newHexData(self.xeth.Coinbase()), nil
+}
+
+func (self *ethApi) IsMining(req *shared.Request) (interface{}, error) {
+	return self.xeth.IsMining(), nil
+}
+
+func (self *ethApi) GasPrice(req *shared.Request) (interface{}, error) {
+	return newHexNum(xeth.DefaultGasPrice().Bytes()), nil
+}
+
+func (self *ethApi) GetStorage(req *shared.Request) (interface{}, error) {
+	args := new(GetStorageArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	return self.xeth.AtStateNum(args.BlockNumber).State().SafeGet(args.Address).Storage(), nil
+}
+
+func (self *ethApi) GetStorageAt(req *shared.Request) (interface{}, error) {
+	args := new(GetStorageAtArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	return self.xeth.AtStateNum(args.BlockNumber).StorageAt(args.Address, args.Key), nil
+}
+
+func (self *ethApi) GetTransactionCount(req *shared.Request) (interface{}, error) {
+	args := new(GetTxCountArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	count := self.xeth.AtStateNum(args.BlockNumber).TxCountAt(args.Address)
+	return newHexNum(big.NewInt(int64(count)).Bytes()), nil
+}
+
+func (self *ethApi) GetBlockTransactionCountByHash(req *shared.Request) (interface{}, error) {
+	args := new(HashArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := NewBlockRes(self.xeth.EthBlockByHash(args.Hash), false)
+	if block == nil {
+		return nil, nil
+	} else {
+		return newHexNum(big.NewInt(int64(len(block.Transactions))).Bytes()), nil
+	}
+}
+
+func (self *ethApi) GetBlockTransactionCountByNumber(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := NewBlockRes(self.xeth.EthBlockByNumber(args.BlockNumber), false)
+	if block == nil {
+		return nil, nil
+	} else {
+		return newHexNum(big.NewInt(int64(len(block.Transactions))).Bytes()), nil
+	}
+}
+
+func (self *ethApi) GetUncleCountByBlockHash(req *shared.Request) (interface{}, error) {
+	args := new(HashArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByHash(args.Hash)
+	br := NewBlockRes(block, false)
+	if br == nil {
+		return nil, nil
+	}
+	return newHexNum(big.NewInt(int64(len(br.Uncles))).Bytes()), nil
+}
+
+func (self *ethApi) GetUncleCountByBlockNumber(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumArg)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	br := NewBlockRes(block, false)
+	if br == nil {
+		return nil, nil
+	}
+	return newHexNum(big.NewInt(int64(len(br.Uncles))).Bytes()), nil
+}
+
+func (self *ethApi) GetData(req *shared.Request) (interface{}, error) {
+	args := new(GetDataArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+	v := self.xeth.AtStateNum(args.BlockNumber).CodeAtBytes(args.Address)
+	return newHexData(v), nil
+}
+
+func (self *ethApi) Sign(req *shared.Request) (interface{}, error) {
+	args := new(NewSigArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+	v, err := self.xeth.Sign(args.From, args.Data, false)
+	if err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+func (self *ethApi) SendTransaction(req *shared.Request) (interface{}, error) {
+	args := new(NewTxArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	// nonce may be nil ("guess" mode)
+	var nonce string
+	if args.Nonce != nil {
+		nonce = args.Nonce.String()
+	}
+
+	v, err := self.xeth.Transact(args.From, args.To, nonce, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
+	if err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+func (self *ethApi) EstimateGas(req *shared.Request) (interface{}, error) {
+	_, gas, err := self.doCall(req.Params)
+	if err != nil {
+		return nil, err
+	}
+
+	// TODO unwrap the parent method's ToHex call
+	if len(gas) == 0 {
+		return newHexNum(0), nil
+	} else {
+		return newHexNum(gas), nil
+	}
+}
+
+func (self *ethApi) Call(req *shared.Request) (interface{}, error) {
+	v, _, err := self.doCall(req.Params)
+	if err != nil {
+		return nil, err
+	}
+
+	// TODO unwrap the parent method's ToHex call
+	if v == "0x0" {
+		return newHexData([]byte{}), nil
+	} else {
+		return newHexData(common.FromHex(v)), nil
+	}
+}
+
+func (self *ethApi) Flush(req *shared.Request) (interface{}, error) {
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *ethApi) doCall(params json.RawMessage) (string, string, error) {
+	args := new(CallArgs)
+	if err := self.codec.Decode(params, &args); err != nil {
+		return "", "", err
+	}
+
+	return self.xeth.AtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
+}
+
+func (self *ethApi) GetBlockByHash(req *shared.Request) (interface{}, error) {
+	args := new(GetBlockByHashArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByHash(args.BlockHash)
+	return NewBlockRes(block, args.IncludeTxs), nil
+}
+
+func (self *ethApi) GetBlockByNumber(req *shared.Request) (interface{}, error) {
+	args := new(GetBlockByNumberArgs)
+	if err := json.Unmarshal(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.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
+	}
+	return br, nil
+}
+
+func (self *ethApi) GetTransactionByHash(req *shared.Request) (interface{}, error) {
+	args := new(HashArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	tx, bhash, bnum, txi := self.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)
+		}
+		return v, nil
+	}
+	return nil, nil
+}
+
+func (self *ethApi) GetTransactionByBlockHashAndIndex(req *shared.Request) (interface{}, error) {
+	args := new(HashIndexArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByHash(args.Hash)
+	br := NewBlockRes(block, true)
+	if br == nil {
+		return nil, nil
+	}
+
+	if args.Index >= int64(len(br.Transactions)) || args.Index < 0 {
+		return nil, nil
+	} else {
+		return br.Transactions[args.Index], nil
+	}
+}
+
+func (self *ethApi) GetTransactionByBlockNumberAndIndex(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumIndexArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	v := NewBlockRes(block, true)
+	if v == nil {
+		return nil, nil
+	}
+
+	if args.Index >= int64(len(v.Transactions)) || args.Index < 0 {
+		// return NewValidationError("Index", "does not exist")
+		return nil, nil
+	}
+	return v.Transactions[args.Index], nil
+}
+
+func (self *ethApi) GetUncleByBlockHashAndIndex(req *shared.Request) (interface{}, error) {
+	args := new(HashIndexArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	br := NewBlockRes(self.xeth.EthBlockByHash(args.Hash), false)
+	if br == nil {
+		return nil, nil
+	}
+
+	if args.Index >= int64(len(br.Uncles)) || args.Index < 0 {
+		// return NewValidationError("Index", "does not exist")
+		return nil, nil
+	}
+
+	return br.Uncles[args.Index], nil
+}
+
+func (self *ethApi) GetUncleByBlockNumberAndIndex(req *shared.Request) (interface{}, error) {
+	args := new(BlockNumIndexArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	block := self.xeth.EthBlockByNumber(args.BlockNumber)
+	v := NewBlockRes(block, true)
+
+	if v == nil {
+		return nil, nil
+	}
+
+	if args.Index >= int64(len(v.Uncles)) || args.Index < 0 {
+		return nil, nil
+	} else {
+		return v.Uncles[args.Index], nil
+	}
+}
+
+func (self *ethApi) GetCompilers(req *shared.Request) (interface{}, error) {
+	var lang string
+	if solc, _ := self.xeth.Solc(); solc != nil {
+		lang = "Solidity"
+	}
+	c := []string{lang}
+	return c, nil
+}
+
+func (self *ethApi) CompileSolidity(req *shared.Request) (interface{}, error) {
+	solc, _ := self.xeth.Solc()
+	if solc == nil {
+		return nil, shared.NewNotAvailableError(req.Method, "solc (solidity compiler) not found")
+	}
+
+	args := new(SourceArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	contracts, err := solc.Compile(args.Source)
+	if err != nil {
+		return nil, err
+	}
+	return contracts, nil
+}
+
+func (self *ethApi) NewFilter(req *shared.Request) (interface{}, error) {
+	args := new(BlockFilterArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	id := self.xeth.NewLogFilter(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics)
+	return newHexNum(big.NewInt(int64(id)).Bytes()), nil
+}
+
+func (self *ethApi) NewBlockFilter(req *shared.Request) (interface{}, error) {
+	return newHexNum(self.xeth.NewBlockFilter()), nil
+}
+
+func (self *ethApi) NewPendingTransactionFilter(req *shared.Request) (interface{}, error) {
+	return newHexNum(self.xeth.NewTransactionFilter()), nil
+}
+
+func (self *ethApi) UninstallFilter(req *shared.Request) (interface{}, error) {
+	args := new(FilterIdArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+	return self.xeth.UninstallFilter(args.Id), nil
+}
+
+func (self *ethApi) GetFilterChanges(req *shared.Request) (interface{}, error) {
+	args := new(FilterIdArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	switch self.xeth.GetFilterType(args.Id) {
+	case xeth.BlockFilterTy:
+		return NewHashesRes(self.xeth.BlockFilterChanged(args.Id)), nil
+	case xeth.TransactionFilterTy:
+		return NewHashesRes(self.xeth.TransactionFilterChanged(args.Id)), nil
+	case xeth.LogFilterTy:
+		return NewLogsRes(self.xeth.LogFilterChanged(args.Id)), nil
+	default:
+		return []string{}, nil // reply empty string slice
+	}
+}
+
+func (self *ethApi) GetFilterLogs(req *shared.Request) (interface{}, error) {
+	args := new(FilterIdArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	return NewLogsRes(self.xeth.Logs(args.Id)), nil
+}
+
+func (self *ethApi) GetLogs(req *shared.Request) (interface{}, error) {
+	args := new(BlockFilterArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+	return NewLogsRes(self.xeth.AllLogs(args.Earliest, args.Latest, args.Skip, args.Max, args.Address, args.Topics)), nil
+}
+
+func (self *ethApi) GetWork(req *shared.Request) (interface{}, error) {
+	self.xeth.SetMining(true, 0)
+	return self.xeth.RemoteMining().GetWork(), nil
+}
+
+func (self *ethApi) SubmitWork(req *shared.Request) (interface{}, error) {
+	args := new(SubmitWorkArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+	return self.xeth.RemoteMining().SubmitWork(args.Nonce, common.HexToHash(args.Digest), common.HexToHash(args.Header)), nil
+}

+ 835 - 0
rpc/api/eth_args.go

@@ -0,0 +1,835 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+const (
+	defaultLogLimit  = 100
+	defaultLogOffset = 0
+)
+
+type GetBalanceArgs struct {
+	Address     string
+	BlockNumber int64
+}
+
+func (args *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	addstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+	args.Address = addstr
+
+	if len(obj) > 1 {
+		if err := blockHeight(obj[1], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type GetStorageArgs struct {
+	Address     string
+	BlockNumber int64
+}
+
+func (args *GetStorageArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	addstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+	args.Address = addstr
+
+	if len(obj) > 1 {
+		if err := blockHeight(obj[1], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type GetStorageAtArgs struct {
+	Address     string
+	BlockNumber int64
+	Key         string
+}
+
+func (args *GetStorageAtArgs) 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)
+	}
+
+	addstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+	args.Address = addstr
+
+	keystr, ok := obj[1].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("key", "not a string")
+	}
+	args.Key = keystr
+
+	if len(obj) > 2 {
+		if err := blockHeight(obj[2], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type GetTxCountArgs struct {
+	Address     string
+	BlockNumber int64
+}
+
+func (args *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	addstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+	args.Address = addstr
+
+	if len(obj) > 1 {
+		if err := blockHeight(obj[1], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type HashArgs struct {
+	Hash string
+}
+
+func (args *HashArgs) 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)
+	}
+
+	arg0, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("hash", "not a string")
+	}
+	args.Hash = arg0
+
+	return nil
+}
+
+type BlockNumArg struct {
+	BlockNumber int64
+}
+
+func (args *BlockNumArg) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	if err := blockHeight(obj[0], &args.BlockNumber); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+type GetDataArgs struct {
+	Address     string
+	BlockNumber int64
+}
+
+func (args *GetDataArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	addstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+	args.Address = addstr
+
+	if len(obj) > 1 {
+		if err := blockHeight(obj[1], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type NewSigArgs struct {
+	From string
+	Data string
+}
+
+func (args *NewSigArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	// Check for sufficient params
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	from, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("from", "not a string")
+	}
+	args.From = from
+
+	if len(args.From) == 0 {
+		return shared.NewValidationError("from", "is required")
+	}
+
+	data, ok := obj[1].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("data", "not a string")
+	}
+	args.Data = data
+
+	if len(args.Data) == 0 {
+		return shared.NewValidationError("data", "is required")
+	}
+
+	return nil
+}
+
+type NewTxArgs struct {
+	From     string
+	To       string
+	Nonce    *big.Int
+	Value    *big.Int
+	Gas      *big.Int
+	GasPrice *big.Int
+	Data     string
+
+	BlockNumber int64
+}
+
+func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []json.RawMessage
+	var ext struct {
+		From     string
+		To       string
+		Nonce    interface{}
+		Value    interface{}
+		Gas      interface{}
+		GasPrice interface{}
+		Data     string
+	}
+
+	// Decode byte slice to array of RawMessages
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	// Check for sufficient params
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	// Decode 0th RawMessage to temporary struct
+	if err := json.Unmarshal(obj[0], &ext); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(ext.From) == 0 {
+		return shared.NewValidationError("from", "is required")
+	}
+
+	args.From = ext.From
+	args.To = ext.To
+	args.Data = ext.Data
+
+	var num *big.Int
+	if ext.Nonce != nil {
+		num, err = numString(ext.Nonce)
+		if err != nil {
+			return err
+		}
+	}
+	args.Nonce = num
+
+	if ext.Value == nil {
+		num = big.NewInt(0)
+	} else {
+		num, err = numString(ext.Value)
+		if err != nil {
+			return err
+		}
+	}
+	args.Value = num
+
+	num = nil
+	if ext.Gas == nil {
+		num = big.NewInt(0)
+	} else {
+		if num, err = numString(ext.Gas); err != nil {
+			return err
+		}
+	}
+	args.Gas = num
+
+	num = nil
+	if ext.GasPrice == nil {
+		num = big.NewInt(0)
+	} else {
+		if num, err = numString(ext.GasPrice); err != nil {
+			return err
+		}
+	}
+	args.GasPrice = num
+
+	// Check for optional BlockNumber param
+	if len(obj) > 1 {
+		if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type SourceArgs struct {
+	Source string
+}
+
+func (args *SourceArgs) 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)
+	}
+
+	arg0, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("source code", "not a string")
+	}
+	args.Source = arg0
+
+	return nil
+}
+
+type CallArgs struct {
+	From     string
+	To       string
+	Value    *big.Int
+	Gas      *big.Int
+	GasPrice *big.Int
+	Data     string
+
+	BlockNumber int64
+}
+
+func (args *CallArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []json.RawMessage
+	var ext struct {
+		From     string
+		To       string
+		Value    interface{}
+		Gas      interface{}
+		GasPrice interface{}
+		Data     string
+	}
+
+	// Decode byte slice to array of RawMessages
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	// Check for sufficient params
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	// Decode 0th RawMessage to temporary struct
+	if err := json.Unmarshal(obj[0], &ext); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	args.From = ext.From
+
+	if len(ext.To) == 0 {
+		return shared.NewValidationError("to", "is required")
+	}
+	args.To = ext.To
+
+	var num *big.Int
+	if ext.Value == nil {
+		num = big.NewInt(0)
+	} else {
+		if num, err = numString(ext.Value); err != nil {
+			return err
+		}
+	}
+	args.Value = num
+
+	if ext.Gas == nil {
+		num = big.NewInt(0)
+	} else {
+		if num, err = numString(ext.Gas); err != nil {
+			return err
+		}
+	}
+	args.Gas = num
+
+	if ext.GasPrice == nil {
+		num = big.NewInt(0)
+	} else {
+		if num, err = numString(ext.GasPrice); err != nil {
+			return err
+		}
+	}
+	args.GasPrice = num
+
+	args.Data = ext.Data
+
+	// Check for optional BlockNumber param
+	if len(obj) > 1 {
+		if err := blockHeightFromJson(obj[1], &args.BlockNumber); err != nil {
+			return err
+		}
+	} else {
+		args.BlockNumber = -1
+	}
+
+	return nil
+}
+
+type HashIndexArgs struct {
+	Hash  string
+	Index int64
+}
+
+func (args *HashIndexArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 2 {
+		return shared.NewInsufficientParamsError(len(obj), 2)
+	}
+
+	arg0, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("hash", "not a string")
+	}
+	args.Hash = arg0
+
+	arg1, ok := obj[1].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("index", "not a string")
+	}
+	args.Index = common.Big(arg1).Int64()
+
+	return nil
+}
+
+type BlockNumIndexArgs struct {
+	BlockNumber int64
+	Index       int64
+}
+
+func (args *BlockNumIndexArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 2 {
+		return shared.NewInsufficientParamsError(len(obj), 2)
+	}
+
+	if err := blockHeight(obj[0], &args.BlockNumber); err != nil {
+		return err
+	}
+
+	var arg1 *big.Int
+	if arg1, err = numString(obj[1]); err != nil {
+		return err
+	}
+	args.Index = arg1.Int64()
+
+	return nil
+}
+
+type GetBlockByHashArgs struct {
+	BlockHash  string
+	IncludeTxs bool
+}
+
+func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 2 {
+		return shared.NewInsufficientParamsError(len(obj), 2)
+	}
+
+	argstr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("blockHash", "not a string")
+	}
+	args.BlockHash = argstr
+
+	args.IncludeTxs = obj[1].(bool)
+
+	return nil
+}
+
+type GetBlockByNumberArgs struct {
+	BlockNumber int64
+	IncludeTxs  bool
+}
+
+func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 2 {
+		return shared.NewInsufficientParamsError(len(obj), 2)
+	}
+
+	if err := blockHeight(obj[0], &args.BlockNumber); err != nil {
+		return err
+	}
+
+	args.IncludeTxs = obj[1].(bool)
+
+	return nil
+}
+
+type BlockFilterArgs struct {
+	Earliest int64
+	Latest   int64
+	Address  []string
+	Topics   [][]string
+	Skip     int
+	Max      int
+}
+
+func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []struct {
+		FromBlock interface{} `json:"fromBlock"`
+		ToBlock   interface{} `json:"toBlock"`
+		Limit     interface{} `json:"limit"`
+		Offset    interface{} `json:"offset"`
+		Address   interface{} `json:"address"`
+		Topics    interface{} `json:"topics"`
+	}
+
+	if err = json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	// args.Earliest, err = toNumber(obj[0].ToBlock)
+	// if err != nil {
+	// 	return shared.NewDecodeParamError(fmt.Sprintf("FromBlock %v", err))
+	// }
+	// args.Latest, err = toNumber(obj[0].FromBlock)
+	// if err != nil {
+	// 	return shared.NewDecodeParamError(fmt.Sprintf("ToBlock %v", err))
+
+	var num int64
+	var numBig *big.Int
+
+	// if blank then latest
+	if obj[0].FromBlock == nil {
+		num = -1
+	} else {
+		if err := blockHeight(obj[0].FromBlock, &num); err != nil {
+			return err
+		}
+	}
+	// if -2 or other "silly" number, use latest
+	if num < 0 {
+		args.Earliest = -1 //latest block
+	} else {
+		args.Earliest = num
+	}
+
+	// if blank than latest
+	if obj[0].ToBlock == nil {
+		num = -1
+	} else {
+		if err := blockHeight(obj[0].ToBlock, &num); err != nil {
+			return err
+		}
+	}
+	args.Latest = num
+
+	if obj[0].Limit == nil {
+		numBig = big.NewInt(defaultLogLimit)
+	} else {
+		if numBig, err = numString(obj[0].Limit); err != nil {
+			return err
+		}
+	}
+	args.Max = int(numBig.Int64())
+
+	if obj[0].Offset == nil {
+		numBig = big.NewInt(defaultLogOffset)
+	} else {
+		if numBig, err = numString(obj[0].Offset); err != nil {
+			return err
+		}
+	}
+	args.Skip = int(numBig.Int64())
+
+	if obj[0].Address != nil {
+		marg, ok := obj[0].Address.([]interface{})
+		if ok {
+			v := make([]string, len(marg))
+			for i, arg := range marg {
+				argstr, ok := arg.(string)
+				if !ok {
+					return shared.NewInvalidTypeError(fmt.Sprintf("address[%d]", i), "is not a string")
+				}
+				v[i] = argstr
+			}
+			args.Address = v
+		} else {
+			argstr, ok := obj[0].Address.(string)
+			if ok {
+				v := make([]string, 1)
+				v[0] = argstr
+				args.Address = v
+			} else {
+				return shared.NewInvalidTypeError("address", "is not a string or array")
+			}
+		}
+	}
+
+	if obj[0].Topics != nil {
+		other, ok := obj[0].Topics.([]interface{})
+		if ok {
+			topicdbl := make([][]string, len(other))
+			for i, iv := range other {
+				if argstr, ok := iv.(string); ok {
+					// Found a string, push into first element of array
+					topicsgl := make([]string, 1)
+					topicsgl[0] = argstr
+					topicdbl[i] = topicsgl
+				} else if argarray, ok := iv.([]interface{}); ok {
+					// Found an array of other
+					topicdbl[i] = make([]string, len(argarray))
+					for j, jv := range argarray {
+						if v, ok := jv.(string); ok {
+							topicdbl[i][j] = v
+						} else if jv == nil {
+							topicdbl[i][j] = ""
+						} else {
+							return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", i, j), "is not a string")
+						}
+					}
+				} else if iv == nil {
+					topicdbl[i] = []string{""}
+				} else {
+					return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d]", i), "not a string or array")
+				}
+			}
+			args.Topics = topicdbl
+			return nil
+		} else {
+			return shared.NewInvalidTypeError("topic", "is not a string or array")
+		}
+	}
+
+	return nil
+}
+
+type FilterIdArgs struct {
+	Id int
+}
+
+func (args *FilterIdArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+
+	var num *big.Int
+	if num, err = numString(obj[0]); err != nil {
+		return err
+	}
+	args.Id = int(num.Int64())
+
+	return nil
+}
+
+type 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
+}
+
+type SubmitWorkArgs struct {
+	Nonce  uint64
+	Header string
+	Digest string
+}
+
+func (args *SubmitWorkArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err = json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 3 {
+		return shared.NewInsufficientParamsError(len(obj), 3)
+	}
+
+	var objstr string
+	var ok bool
+	if objstr, ok = obj[0].(string); !ok {
+		return shared.NewInvalidTypeError("nonce", "not a string")
+	}
+
+	args.Nonce = common.String2Big(objstr).Uint64()
+	if objstr, ok = obj[1].(string); !ok {
+		return shared.NewInvalidTypeError("header", "not a string")
+	}
+
+	args.Header = objstr
+
+	if objstr, ok = obj[2].(string); !ok {
+		return shared.NewInvalidTypeError("digest", "not a string")
+	}
+
+	args.Digest = objstr
+
+	return nil
+}

+ 3 - 0
rpc/api/eth_js.go

@@ -0,0 +1,3 @@
+package api
+
+// JS api provided by web3.js

+ 66 - 0
rpc/api/mergedapi.go

@@ -0,0 +1,66 @@
+package api
+
+import (
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+const (
+	MergedApiVersion = "1.0"
+)
+
+// combines multiple API's
+type MergedApi struct {
+	apis    map[string]string
+	methods map[string]EthereumApi
+}
+
+// create new merged api instance
+func newMergedApi(apis ...EthereumApi) *MergedApi {
+	mergedApi := new(MergedApi)
+	mergedApi.apis = make(map[string]string, len(apis))
+	mergedApi.methods = make(map[string]EthereumApi)
+
+	for _, api := range apis {
+		mergedApi.apis[api.Name()] = api.ApiVersion()
+		for _, method := range api.Methods() {
+			mergedApi.methods[method] = api
+		}
+	}
+	return mergedApi
+}
+
+// Supported RPC methods
+func (self *MergedApi) Methods() []string {
+	all := make([]string, len(self.methods))
+	for method, _ := range self.methods {
+		all = append(all, method)
+	}
+	return all
+}
+
+// Call the correct API's Execute method for the given request
+func (self *MergedApi) Execute(req *shared.Request) (interface{}, error) {
+	if res, _ := self.handle(req); res != nil {
+		return res, nil
+	}
+	if api, found := self.methods[req.Method]; found {
+		return api.Execute(req)
+	}
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *MergedApi) Name() string {
+	return MergedApiName
+}
+
+func (self *MergedApi) ApiVersion() string {
+	return MergedApiVersion
+}
+
+func (self *MergedApi) handle(req *shared.Request) (interface{}, error) {
+	if req.Method == "modules" { // provided API's
+		return self.apis, nil
+	}
+
+	return nil, nil
+}

+ 1 - 0
rpc/api/mergedapi_js.go

@@ -0,0 +1 @@
+package api

+ 147 - 0
rpc/api/miner.go

@@ -0,0 +1,147 @@
+package api
+
+import (
+	"github.com/ethereum/ethash"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+const (
+	MinerApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	MinerMapping = map[string]minerhandler{
+		"miner_hashrate":     (*minerApi).Hashrate,
+		"miner_makeDAG":      (*minerApi).MakeDAG,
+		"miner_setExtra":     (*minerApi).SetExtra,
+		"miner_setGasPrice":  (*minerApi).SetGasPrice,
+		"miner_startAutoDAG": (*minerApi).StartAutoDAG,
+		"miner_start":        (*minerApi).StartMiner,
+		"miner_stopAutoDAG":  (*minerApi).StopAutoDAG,
+		"miner_stop":         (*minerApi).StopMiner,
+	}
+)
+
+// miner callback handler
+type minerhandler func(*minerApi, *shared.Request) (interface{}, error)
+
+// miner api provider
+type minerApi struct {
+	ethereum *eth.Ethereum
+	methods  map[string]minerhandler
+	codec    codec.ApiCoder
+}
+
+// create a new miner api instance
+func NewMinerApi(ethereum *eth.Ethereum, coder codec.Codec) *minerApi {
+	return &minerApi{
+		ethereum: ethereum,
+		methods:  MinerMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// Execute given request
+func (self *minerApi) Execute(req *shared.Request) (interface{}, error) {
+	if callback, ok := self.methods[req.Method]; ok {
+		return callback(self, req)
+	}
+
+	return nil, &shared.NotImplementedError{req.Method}
+}
+
+// collection with supported methods
+func (self *minerApi) Methods() []string {
+	methods := make([]string, len(self.methods))
+	i := 0
+	for k := range self.methods {
+		methods[i] = k
+		i++
+	}
+	return methods
+}
+
+func (self *minerApi) Name() string {
+	return MinerApiName
+}
+
+func (self *minerApi) ApiVersion() string {
+	return MinerApiVersion
+}
+
+func (self *minerApi) StartMiner(req *shared.Request) (interface{}, error) {
+	args := new(StartMinerArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+	if args.Threads == -1 { // (not specified by user, use default)
+		args.Threads = self.ethereum.MinerThreads
+	}
+
+	self.ethereum.StartAutoDAG()
+	err := self.ethereum.StartMining(args.Threads)
+	if err == nil {
+		return true, nil
+	}
+
+	return false, err
+}
+
+func (self *minerApi) StopMiner(req *shared.Request) (interface{}, error) {
+	self.ethereum.StopMining()
+	return true, nil
+}
+
+func (self *minerApi) Hashrate(req *shared.Request) (interface{}, error) {
+	return self.ethereum.Miner().HashRate(), nil
+}
+
+func (self *minerApi) SetExtra(req *shared.Request) (interface{}, error) {
+	args := new(SetExtraArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+	self.ethereum.Miner().SetExtra([]byte(args.Data))
+	return true, nil
+}
+
+func (self *minerApi) SetGasPrice(req *shared.Request) (interface{}, error) {
+	args := new(GasPriceArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return false, err
+	}
+
+	self.ethereum.Miner().SetGasPrice(common.String2Big(args.Price))
+	return true, nil
+}
+
+func (self *minerApi) StartAutoDAG(req *shared.Request) (interface{}, error) {
+	self.ethereum.StartAutoDAG()
+	return true, nil
+}
+
+func (self *minerApi) StopAutoDAG(req *shared.Request) (interface{}, error) {
+	self.ethereum.StopAutoDAG()
+	return true, nil
+}
+
+func (self *minerApi) MakeDAG(req *shared.Request) (interface{}, error) {
+	args := new(MakeDAGArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	if args.BlockNumber < 0 {
+		return false, shared.NewValidationError("BlockNumber", "BlockNumber must be positive")
+	}
+
+	err := ethash.MakeDAG(uint64(args.BlockNumber), "")
+	if err == nil {
+		return true, nil
+	}
+	return false, err
+}

+ 100 - 0
rpc/api/miner_args.go

@@ -0,0 +1,100 @@
+package api
+
+import (
+	"encoding/json"
+
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+type StartMinerArgs struct {
+	Threads int
+}
+
+func (args *StartMinerArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) == 0 || obj[0] == nil {
+		args.Threads = -1
+		return nil
+	}
+
+	var num *big.Int
+	if num, err = numString(obj[0]); err != nil {
+		return err
+	}
+	args.Threads = int(num.Int64())
+	return nil
+}
+
+type SetExtraArgs struct {
+	Data string
+}
+
+func (args *SetExtraArgs) 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)
+	}
+
+	extrastr, ok := obj[0].(string)
+	if !ok {
+		return shared.NewInvalidTypeError("Price", "not a string")
+	}
+	args.Data = extrastr
+
+	return nil
+}
+
+type GasPriceArgs struct {
+	Price string
+}
+
+func (args *GasPriceArgs) 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)
+	}
+
+	if pricestr, ok := obj[0].(string); ok {
+		args.Price = pricestr
+		return nil
+	}
+
+	return shared.NewInvalidTypeError("Price", "not a string")
+}
+
+type MakeDAGArgs struct {
+	BlockNumber int64
+}
+
+func (args *MakeDAGArgs) UnmarshalJSON(b []byte) (err error) {
+	args.BlockNumber = -1
+	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)
+	}
+
+	if err := blockHeight(obj[0], &args.BlockNumber); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 67 - 0
rpc/api/miner_js.go

@@ -0,0 +1,67 @@
+package api
+
+const Miner_JS = `
+web3._extend({
+	property: 'miner',
+	methods:
+	[
+		new web3._extend.Method({
+			name: 'start',
+			call: 'miner_start',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'stop',
+			call: 'miner_stop',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'setExtra',
+			call: 'miner_setExtra',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'setGasPrice',
+			call: 'miner_setGasPrice',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'startAutoDAG',
+			call: 'miner_startAutoDAG',
+			params: 0,
+			inputFormatter: [],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'stopAutoDAG',
+			call: 'miner_stopAutoDAG',
+			params: 0,
+			inputFormatter: [],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'makeDAG',
+			call: 'miner_makeDAG',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.inputDefaultBlockNumberFormatter],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		})
+	],
+	properties:
+	[
+		new web3._extend.Property({
+			name: 'hashrate',
+			getter: 'miner_hashrate',
+			outputFormatter: web3._extend.utils.toDecimal
+		})
+	]
+});
+`

+ 89 - 0
rpc/api/net.go

@@ -0,0 +1,89 @@
+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 (
+	NetApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	netMapping = map[string]nethandler{
+		"net_version":   (*netApi).Version,
+		"net_peerCount": (*netApi).PeerCount,
+		"net_listening": (*netApi).IsListening,
+		"net_peers":     (*netApi).Peers,
+	}
+)
+
+// net callback handler
+type nethandler func(*netApi, *shared.Request) (interface{}, error)
+
+// net api provider
+type netApi struct {
+	xeth     *xeth.XEth
+	ethereum *eth.Ethereum
+	methods  map[string]nethandler
+	codec    codec.ApiCoder
+}
+
+// create a new net api instance
+func NewNetApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *netApi {
+	return &netApi{
+		xeth:     xeth,
+		ethereum: eth,
+		methods:  netMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *netApi) 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 *netApi) Execute(req *shared.Request) (interface{}, error) {
+	if callback, ok := self.methods[req.Method]; ok {
+		return callback(self, req)
+	}
+
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *netApi) Name() string {
+	return NetApiName
+}
+
+func (self *netApi) ApiVersion() string {
+	return NetApiVersion
+}
+
+// Network version
+func (self *netApi) Version(req *shared.Request) (interface{}, error) {
+	return self.xeth.NetworkVersion(), nil
+}
+
+// Number of connected peers
+func (self *netApi) PeerCount(req *shared.Request) (interface{}, error) {
+	return self.xeth.PeerCount(), nil
+}
+
+func (self *netApi) IsListening(req *shared.Request) (interface{}, error) {
+	return self.xeth.IsListening(), nil
+}
+
+func (self *netApi) Peers(req *shared.Request) (interface{}, error) {
+	return self.ethereum.PeersInfo(), nil
+}

+ 47 - 0
rpc/api/net_js.go

@@ -0,0 +1,47 @@
+package api
+
+const Net_JS = `
+web3._extend({
+	property: 'network',
+	methods:
+	[
+		new web3._extend.Method({
+			name: 'addPeer',
+			call: 'net_addPeer',
+			params: 1,
+			inputFormatter: [web3._extend.utils.formatInputString],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Method({
+			name: 'getPeerCount',
+			call: 'net_peerCount',
+			params: 0,
+			inputFormatter: [],
+			outputFormatter: web3._extend.formatters.formatOutputString
+		})
+	],
+	properties:
+	[
+		new web3._extend.Property({
+			name: 'listening',
+			getter: 'net_listening',
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+		new web3._extend.Property({
+			name: 'peerCount',
+			getter: 'net_peerCount',
+			outputFormatter: web3._extend.utils.toDecimal
+		}),
+		new web3._extend.Property({
+			name: 'peers',
+			getter: 'net_peers',
+			outputFormatter: function(obj) { return obj; }
+		}),
+		new web3._extend.Property({
+			name: 'version',
+			getter: 'net_version',
+			outputFormatter: web3._extend.formatters.formatOutputString
+		})
+	]
+});
+`

+ 460 - 0
rpc/api/parsing.go

@@ -0,0 +1,460 @@
+package api
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"encoding/json"
+	"math/big"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+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 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"`
+// }
+
+func numString(raw interface{}) (*big.Int, error) {
+	var number *big.Int
+	// Parse as integer
+	num, ok := raw.(float64)
+	if ok {
+		number = big.NewInt(int64(num))
+		return number, nil
+	}
+
+	// Parse as string/hexstring
+	str, ok := raw.(string)
+	if ok {
+		number = common.String2Big(str)
+		return number, nil
+	}
+
+	return nil, shared.NewInvalidTypeError("", "not a number or string")
+}
+
+func blockHeight(raw interface{}, number *int64) error {
+	// Parse as integer
+	num, ok := raw.(float64)
+	if ok {
+		*number = int64(num)
+		return nil
+	}
+
+	// Parse as string/hexstring
+	str, ok := raw.(string)
+	if !ok {
+		return shared.NewInvalidTypeError("", "not a number or string")
+	}
+
+	switch str {
+	case "earliest":
+		*number = 0
+	case "latest":
+		*number = -1
+	case "pending":
+		*number = -2
+	default:
+		if common.HasHexPrefix(str) {
+			*number = common.String2Big(str).Int64()
+		} else {
+			return shared.NewInvalidTypeError("blockNumber", "is not a valid string")
+		}
+	}
+
+	return nil
+}
+
+func blockHeightFromJson(msg json.RawMessage, number *int64) error {
+	var raw interface{}
+	if err := json.Unmarshal(msg, &raw); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+	return blockHeight(raw, number)
+}

+ 126 - 0
rpc/api/personal.go

@@ -0,0 +1,126 @@
+package api
+
+import (
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"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 (
+	PersonalApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	personalMapping = map[string]personalhandler{
+		"personal_listAccounts":  (*personalApi).ListAccounts,
+		"personal_newAccount":    (*personalApi).NewAccount,
+		"personal_deleteAccount": (*personalApi).DeleteAccount,
+		"personal_unlockAccount": (*personalApi).UnlockAccount,
+	}
+)
+
+// net callback handler
+type personalhandler func(*personalApi, *shared.Request) (interface{}, error)
+
+// net api provider
+type personalApi struct {
+	xeth     *xeth.XEth
+	ethereum *eth.Ethereum
+	methods  map[string]personalhandler
+	codec    codec.ApiCoder
+}
+
+// create a new net api instance
+func NewPersonalApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *personalApi {
+	return &personalApi{
+		xeth:     xeth,
+		ethereum: eth,
+		methods:  personalMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *personalApi) 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 *personalApi) Execute(req *shared.Request) (interface{}, error) {
+	if callback, ok := self.methods[req.Method]; ok {
+		return callback(self, req)
+	}
+
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *personalApi) Name() string {
+	return PersonalApiName
+}
+
+func (self *personalApi) ApiVersion() string {
+	return PersonalApiVersion
+}
+
+func (self *personalApi) ListAccounts(req *shared.Request) (interface{}, error) {
+	return self.xeth.Accounts(), nil
+}
+
+func (self *personalApi) NewAccount(req *shared.Request) (interface{}, error) {
+	args := new(NewAccountArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	am := self.ethereum.AccountManager()
+	acc, err := am.NewAccount(args.Passphrase)
+	return acc.Address.Hex(), err
+}
+
+func (self *personalApi) DeleteAccount(req *shared.Request) (interface{}, error) {
+	args := new(DeleteAccountArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	addr := common.HexToAddress(args.Address)
+	am := self.ethereum.AccountManager()
+	if err := am.DeleteAccount(addr, args.Passphrase); err == nil {
+		return true, nil
+	} else {
+		return false, err
+	}
+}
+
+func (self *personalApi) UnlockAccount(req *shared.Request) (interface{}, error) {
+	args := new(UnlockAccountArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	var err error
+	am := self.ethereum.AccountManager()
+	addr := common.HexToAddress(args.Address)
+
+	if args.Duration == -1 {
+		err = am.Unlock(addr, args.Passphrase)
+	} else {
+		err = am.TimedUnlock(addr, args.Passphrase, time.Duration(args.Duration)*time.Second)
+	}
+
+	if err == nil {
+		return true, nil
+	}
+	return false, err
+}

+ 92 - 0
rpc/api/personal_args.go

@@ -0,0 +1,92 @@
+package api
+
+import (
+	"encoding/json"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+type NewAccountArgs struct {
+	Passphrase string
+}
+
+func (args *NewAccountArgs) 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)
+	}
+
+	if passhrase, ok := obj[0].(string); ok {
+		args.Passphrase = passhrase
+		return nil
+	}
+
+	return shared.NewInvalidTypeError("passhrase", "not a string")
+}
+
+type DeleteAccountArgs struct {
+	Address    string
+	Passphrase string
+}
+
+func (args *DeleteAccountArgs) 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)
+	}
+
+	if addr, ok := obj[0].(string); ok {
+		args.Address = addr
+	} else {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+
+	if passhrase, ok := obj[1].(string); ok {
+		args.Passphrase = passhrase
+	} else {
+		return shared.NewInvalidTypeError("passhrase", "not a string")
+	}
+
+	return nil
+}
+
+type UnlockAccountArgs struct {
+	Address    string
+	Passphrase string
+	Duration   int
+}
+
+func (args *UnlockAccountArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []interface{}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	args.Duration = -1
+
+	if len(obj) < 2 {
+		return shared.NewInsufficientParamsError(len(obj), 2)
+	}
+
+	if addrstr, ok := obj[0].(string); ok {
+		args.Address = addrstr
+	} else {
+		return shared.NewInvalidTypeError("address", "not a string")
+	}
+
+	if passphrasestr, ok := obj[1].(string); ok {
+		args.Passphrase = passphrasestr
+	} else {
+		return shared.NewInvalidTypeError("passphrase", "not a string")
+	}
+
+	return nil
+}

+ 32 - 0
rpc/api/personal_js.go

@@ -0,0 +1,32 @@
+package api
+
+const Personal_JS = `
+web3._extend({
+	property: 'personal',
+	methods:
+	[
+		new web3._extend.Method({
+			name: 'newAccount',
+			call: 'personal_newAccount',
+			params: 1,
+			inputFormatter: [web3._extend.formatters.formatInputString],
+			outputFormatter: web3._extend.formatters.formatOutputString
+		}),
+		new web3._extend.Method({
+			name: 'unlockAccount',
+			call: 'personal_unlockAccount',
+			params: 3,
+			inputFormatter: [web3._extend.formatters.formatInputString,web3._extend.formatters.formatInputString,web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		})
+	],
+	properties:
+	[
+		new web3._extend.Property({
+			name: 'listAccounts',
+			getter: 'personal_listAccounts',
+			outputFormatter: function(obj) { return obj; }
+		})
+	]
+});
+`

+ 179 - 0
rpc/api/shh.go

@@ -0,0 +1,179 @@
+package api
+
+import (
+	"math/big"
+
+	"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 (
+	ShhApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	shhMapping = map[string]shhhandler{
+		"shh_version":          (*shhApi).Version,
+		"shh_post":             (*shhApi).Post,
+		"shh_hasIdentity":      (*shhApi).HasIdentity,
+		"shh_newIdentity":      (*shhApi).NewIdentity,
+		"shh_newFilter":        (*shhApi).NewFilter,
+		"shh_uninstallFilter":  (*shhApi).UninstallFilter,
+		"shh_getFilterChanges": (*shhApi).GetFilterChanges,
+	}
+)
+
+func newWhisperOfflineError(method string) error {
+	return shared.NewNotAvailableError(method, "whisper offline")
+}
+
+// net callback handler
+type shhhandler func(*shhApi, *shared.Request) (interface{}, error)
+
+// shh api provider
+type shhApi struct {
+	xeth     *xeth.XEth
+	ethereum *eth.Ethereum
+	methods  map[string]shhhandler
+	codec    codec.ApiCoder
+}
+
+// create a new whisper api instance
+func NewShhApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *shhApi {
+	return &shhApi{
+		xeth:     xeth,
+		ethereum: eth,
+		methods:  shhMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *shhApi) 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 *shhApi) Execute(req *shared.Request) (interface{}, error) {
+	if callback, ok := self.methods[req.Method]; ok {
+		return callback(self, req)
+	}
+
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *shhApi) Name() string {
+	return ShhApiName
+}
+
+func (self *shhApi) ApiVersion() string {
+	return ShhApiVersion
+}
+
+func (self *shhApi) Version(req *shared.Request) (interface{}, error) {
+	w := self.xeth.Whisper()
+	if w == nil {
+		return nil, newWhisperOfflineError(req.Method)
+	}
+
+	return w.Version(), nil
+}
+
+func (self *shhApi) Post(req *shared.Request) (interface{}, error) {
+	w := self.xeth.Whisper()
+	if w == nil {
+		return nil, newWhisperOfflineError(req.Method)
+	}
+
+	args := new(WhisperMessageArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	err := w.Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl)
+	if err != nil {
+		return false, err
+	}
+
+	return true, nil
+}
+
+func (self *shhApi) HasIdentity(req *shared.Request) (interface{}, error) {
+	w := self.xeth.Whisper()
+	if w == nil {
+		return nil, newWhisperOfflineError(req.Method)
+	}
+
+	args := new(WhisperIdentityArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	return w.HasIdentity(args.Identity), nil
+}
+
+func (self *shhApi) NewIdentity(req *shared.Request) (interface{}, error) {
+	w := self.xeth.Whisper()
+	if w == nil {
+		return nil, newWhisperOfflineError(req.Method)
+	}
+
+	return w.NewIdentity(), nil
+}
+
+func (self *shhApi) NewFilter(req *shared.Request) (interface{}, error) {
+	args := new(WhisperFilterArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	id := self.xeth.NewWhisperFilter(args.To, args.From, args.Topics)
+	return newHexNum(big.NewInt(int64(id)).Bytes()), nil
+}
+
+func (self *shhApi) UninstallFilter(req *shared.Request) (interface{}, error) {
+	args := new(FilterIdArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+	return self.xeth.UninstallWhisperFilter(args.Id), nil
+}
+
+func (self *shhApi) GetFilterChanges(req *shared.Request) (interface{}, error) {
+	w := self.xeth.Whisper()
+	if w == nil {
+		return nil, newWhisperOfflineError(req.Method)
+	}
+
+	// Retrieve all the new messages arrived since the last request
+	args := new(FilterIdArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	return self.xeth.WhisperMessagesChanged(args.Id), nil
+}
+
+func (self *shhApi) GetMessages(req *shared.Request) (interface{}, error) {
+	w := self.xeth.Whisper()
+	if w == nil {
+		return nil, newWhisperOfflineError(req.Method)
+	}
+
+	// Retrieve all the cached messages matching a specific, existing filter
+	args := new(FilterIdArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	return self.xeth.WhisperMessages(args.Id), nil
+}

+ 158 - 0
rpc/api/shh_args.go

@@ -0,0 +1,158 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+type WhisperMessageArgs struct {
+	Payload  string
+	To       string
+	From     string
+	Topics   []string
+	Priority uint32
+	Ttl      uint32
+}
+
+func (args *WhisperMessageArgs) UnmarshalJSON(b []byte) (err error) {
+	var obj []struct {
+		Payload  string
+		To       string
+		From     string
+		Topics   []string
+		Priority interface{}
+		Ttl      interface{}
+	}
+
+	if err = json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+	args.Payload = obj[0].Payload
+	args.To = obj[0].To
+	args.From = obj[0].From
+	args.Topics = obj[0].Topics
+
+	var num *big.Int
+	if num, err = numString(obj[0].Priority); err != nil {
+		return err
+	}
+	args.Priority = uint32(num.Int64())
+
+	if num, err = numString(obj[0].Ttl); err != nil {
+		return err
+	}
+	args.Ttl = uint32(num.Int64())
+
+	return nil
+}
+
+type WhisperIdentityArgs struct {
+	Identity string
+}
+
+func (args *WhisperIdentityArgs) 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", "not a string")
+	}
+
+	args.Identity = argstr
+
+	return nil
+}
+
+type WhisperFilterArgs struct {
+	To     string
+	From   string
+	Topics [][]string
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
+// JSON message blob into a WhisperFilterArgs structure.
+func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
+	// Unmarshal the JSON message and sanity check
+	var obj []struct {
+		To     interface{} `json:"to"`
+		From   interface{} `json:"from"`
+		Topics interface{} `json:"topics"`
+	}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return shared.NewDecodeParamError(err.Error())
+	}
+	if len(obj) < 1 {
+		return shared.NewInsufficientParamsError(len(obj), 1)
+	}
+	// Retrieve the simple data contents of the filter arguments
+	if obj[0].To == nil {
+		args.To = ""
+	} else {
+		argstr, ok := obj[0].To.(string)
+		if !ok {
+			return shared.NewInvalidTypeError("to", "is not a string")
+		}
+		args.To = argstr
+	}
+	if obj[0].From == nil {
+		args.From = ""
+	} else {
+		argstr, ok := obj[0].From.(string)
+		if !ok {
+			return shared.NewInvalidTypeError("from", "is not a string")
+		}
+		args.From = argstr
+	}
+	// Construct the nested topic array
+	if obj[0].Topics != nil {
+		// Make sure we have an actual topic array
+		list, ok := obj[0].Topics.([]interface{})
+		if !ok {
+			return shared.NewInvalidTypeError("topics", "is not an array")
+		}
+		// Iterate over each topic and handle nil, string or array
+		topics := make([][]string, len(list))
+		for idx, field := range list {
+			switch value := field.(type) {
+			case nil:
+				topics[idx] = []string{}
+
+			case string:
+				topics[idx] = []string{value}
+
+			case []interface{}:
+				topics[idx] = make([]string, len(value))
+				for i, nested := range value {
+					switch value := nested.(type) {
+					case nil:
+						topics[idx][i] = ""
+
+					case string:
+						topics[idx][i] = value
+
+					default:
+						return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", idx, i), "is not a string")
+					}
+				}
+			default:
+				return shared.NewInvalidTypeError(fmt.Sprintf("topic[%d]", idx), "not a string or array")
+			}
+		}
+		args.Topics = topics
+	}
+	return nil
+}

+ 30 - 0
rpc/api/ssh_js.go

@@ -0,0 +1,30 @@
+package api
+
+const Shh_JS = `
+web3._extend({
+	property: 'shh',
+	methods:
+	[
+		new web3._extend.Method({
+			name: 'post',
+			call: 'shh_post',
+			params: 6,
+			inputFormatter: [web3._extend.formatters.formatInputString,
+							  web3._extend.formatters.formatInputString,
+							  web3._extend.formatters.formatInputString,
+							,
+							, web3._extend.formatters.formatInputInt
+							, web3._extend.formatters.formatInputInt],
+			outputFormatter: web3._extend.formatters.formatOutputBool
+		}),
+	],
+	properties:
+	[
+		new web3._extend.Property({
+			name: 'version',
+			getter: 'shh_version',
+			outputFormatter: web3._extend.formatters.formatOutputInt
+		})
+	]
+});
+`

+ 75 - 0
rpc/api/txpool.go

@@ -0,0 +1,75 @@
+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 (
+	TxPoolApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	txpoolMapping = map[string]txpoolhandler{
+		"txpool_status": (*txPoolApi).Status,
+	}
+)
+
+// net callback handler
+type txpoolhandler func(*txPoolApi, *shared.Request) (interface{}, error)
+
+// txpool api provider
+type txPoolApi struct {
+	xeth     *xeth.XEth
+	ethereum *eth.Ethereum
+	methods  map[string]txpoolhandler
+	codec    codec.ApiCoder
+}
+
+// create a new txpool api instance
+func NewTxPoolApi(xeth *xeth.XEth, eth *eth.Ethereum, coder codec.Codec) *txPoolApi {
+	return &txPoolApi{
+		xeth:     xeth,
+		ethereum: eth,
+		methods:  txpoolMapping,
+		codec:    coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *txPoolApi) 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 *txPoolApi) Execute(req *shared.Request) (interface{}, error) {
+	if callback, ok := self.methods[req.Method]; ok {
+		return callback(self, req)
+	}
+
+	return nil, shared.NewNotImplementedError(req.Method)
+}
+
+func (self *txPoolApi) Name() string {
+	return TxPoolApiName
+}
+
+func (self *txPoolApi) ApiVersion() string {
+	return TxPoolApiVersion
+}
+
+func (self *txPoolApi) Status(req *shared.Request) (interface{}, error) {
+	return map[string]int{
+		"pending": self.ethereum.TxPool().GetTransactions().Len(),
+		"queued":  self.ethereum.TxPool().GetQueuedTransactions().Len(),
+	}, nil
+}

+ 18 - 0
rpc/api/txpool_js.go

@@ -0,0 +1,18 @@
+package api
+
+const TxPool_JS = `
+web3._extend({
+	property: 'txpool',
+	methods:
+	[
+	],
+	properties:
+	[
+		new web3._extend.Property({
+			name: 'status',
+			getter: 'txpool_status',
+			outputFormatter: function(obj) { return obj; }
+		})
+	]
+});
+`

+ 180 - 0
rpc/api/utils.go

@@ -0,0 +1,180 @@
+package api
+
+import (
+	"strings"
+
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/xeth"
+)
+
+var (
+	// Mapping between the different methods each api supports
+	AutoCompletion = map[string][]string{
+		"admin": []string{
+			"addPeer",
+			"peers",
+			"nodeInfo",
+			"exportChain",
+			"importChain",
+			"verbosity",
+			"chainSyncStatus",
+			"setSolc",
+			"datadir",
+		},
+		"debug": []string{
+			"dumpBlock",
+			"getBlockRlp",
+			"printBlock",
+			"processBlock",
+			"seedHash",
+			"setHead",
+		},
+		"eth": []string{
+			"accounts",
+			"blockNumber",
+			"getBalance",
+			"protocolVersion",
+			"coinbase",
+			"mining",
+			"gasPrice",
+			"getStorage",
+			"storageAt",
+			"getStorageAt",
+			"getTransactionCount",
+			"getBlockTransactionCountByHash",
+			"getBlockTransactionCountByNumber",
+			"getUncleCountByBlockHash",
+			"getUncleCountByBlockNumber",
+			"getData",
+			"getCode",
+			"sign",
+			"sendTransaction",
+			"transact",
+			"estimateGas",
+			"call",
+			"flush",
+			"getBlockByHash",
+			"getBlockByNumber",
+			"getTransactionByHash",
+			"getTransactionByBlockHashAndIndex",
+			"getUncleByBlockHashAndIndex",
+			"getUncleByBlockNumberAndIndex",
+			"getCompilers",
+			"compileSolidity",
+			"newFilter",
+			"newBlockFilter",
+			"newPendingTransactionFilter",
+			"uninstallFilter",
+			"getFilterChanges",
+			"getFilterLogs",
+			"getLogs",
+			"hashrate",
+			"getWork",
+			"submitWork",
+		},
+		"miner": []string{
+			"hashrate",
+			"makeDAG",
+			"setExtra",
+			"setGasPrice",
+			"startAutoDAG",
+			"start",
+			"stopAutoDAG",
+			"stop",
+		},
+		"net": []string{
+			"peerCount",
+			"listening",
+		},
+		"personal": []string{
+			"listAccounts",
+			"newAccount",
+			"deleteAccount",
+			"unlockAccount",
+		},
+		"shh": []string{
+			"version",
+			"post",
+			"hasIdentity",
+			"newIdentity",
+			"newFilter",
+			"uninstallFilter",
+			"getFilterChanges",
+		},
+		"txpool": []string{
+			"status",
+		},
+		"web3": []string{
+			"sha3",
+			"version",
+			"fromWei",
+			"toWei",
+			"toHex",
+			"toAscii",
+			"fromAscii",
+			"toBigNumber",
+			"isAddress",
+		},
+	}
+)
+
+// 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) {
+	if len(strings.TrimSpace(apistr)) == 0 {
+		return nil, fmt.Errorf("Empty apistr provided")
+	}
+
+	names := strings.Split(apistr, ",")
+	apis := make([]EthereumApi, len(names))
+
+	for i, name := range names {
+		switch strings.ToLower(strings.TrimSpace(name)) {
+		case AdminApiName:
+			apis[i] = NewAdminApi(xeth, eth, codec)
+		case DebugApiName:
+			apis[i] = NewDebugApi(xeth, eth, codec)
+		case EthApiName:
+			apis[i] = NewEthApi(xeth, codec)
+		case MinerApiName:
+			apis[i] = NewMinerApi(eth, codec)
+		case NetApiName:
+			apis[i] = NewNetApi(xeth, eth, codec)
+		case ShhApiName:
+			apis[i] = NewShhApi(xeth, eth, codec)
+		case TxPoolApiName:
+			apis[i] = NewTxPoolApi(xeth, eth, codec)
+		case PersonalApiName:
+			apis[i] = NewPersonalApi(xeth, eth, codec)
+		case Web3ApiName:
+			apis[i] = NewWeb3Api(xeth, codec)
+		default:
+			return nil, fmt.Errorf("Unknown API '%s'", name)
+		}
+	}
+
+	return apis, nil
+}
+
+func Javascript(name string) string {
+	switch strings.ToLower(strings.TrimSpace(name)) {
+	case AdminApiName:
+		return Admin_JS
+	case DebugApiName:
+		return Debug_JS
+	case MinerApiName:
+		return Miner_JS
+	case NetApiName:
+		return Net_JS
+	case ShhApiName:
+		return Shh_JS
+	case TxPoolApiName:
+		return TxPool_JS
+	case PersonalApiName:
+		return Personal_JS
+	}
+
+	return ""
+}

+ 83 - 0
rpc/api/web3.go

@@ -0,0 +1,83 @@
+package api
+
+import (
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+	"github.com/ethereum/go-ethereum/xeth"
+)
+
+const (
+	Web3ApiVersion = "1.0"
+)
+
+var (
+	// mapping between methods and handlers
+	Web3Mapping = map[string]web3handler{
+		"web3_sha3":          (*web3Api).Sha3,
+		"web3_clientVersion": (*web3Api).ClientVersion,
+	}
+)
+
+// web3 callback handler
+type web3handler func(*web3Api, *shared.Request) (interface{}, error)
+
+// web3 api provider
+type web3Api struct {
+	xeth    *xeth.XEth
+	methods map[string]web3handler
+	codec   codec.ApiCoder
+}
+
+// create a new web3 api instance
+func NewWeb3Api(xeth *xeth.XEth, coder codec.Codec) *web3Api {
+	return &web3Api{
+		xeth:    xeth,
+		methods: Web3Mapping,
+		codec:   coder.New(nil),
+	}
+}
+
+// collection with supported methods
+func (self *web3Api) 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 *web3Api) 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 *web3Api) Name() string {
+	return Web3ApiName
+}
+
+func (self *web3Api) ApiVersion() string {
+	return Web3ApiVersion
+}
+
+// Calculates the sha3 over req.Params.Data
+func (self *web3Api) Sha3(req *shared.Request) (interface{}, error) {
+	args := new(Sha3Args)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, err
+	}
+
+	return common.ToHex(crypto.Sha3(common.FromHex(args.Data))), nil
+}
+
+// returns the xeth client vrsion
+func (self *web3Api) ClientVersion(req *shared.Request) (interface{}, error) {
+	return self.xeth.ClientVersion(), nil
+}

+ 5 - 0
rpc/api/web3_args.go

@@ -0,0 +1,5 @@
+package api
+
+type Sha3Args struct {
+	Data string
+}

+ 47 - 0
rpc/codec/codec.go

@@ -0,0 +1,47 @@
+package codec
+
+import (
+	"net"
+	"strconv"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+type Codec int
+
+// (de)serialization support for rpc interface
+type ApiCoder interface {
+	// Parse message to request from underlying stream
+	ReadRequest() (*shared.Request, error)
+	// Parse response message from underlying stream
+	ReadResponse() (interface{}, error)
+	// Encode response to encoded form in underlying stream
+	WriteResponse(interface{}) error
+	// Decode single message from data
+	Decode([]byte, interface{}) error
+	// Encode msg to encoded form
+	Encode(msg interface{}) ([]byte, error)
+	// close the underlying stream
+	Close()
+}
+
+// supported codecs
+const (
+	JSON Codec = iota
+	nCodecs
+)
+
+var (
+	// collection with supported coders
+	coders = make([]func(net.Conn) ApiCoder, nCodecs)
+)
+
+// create a new coder instance
+func (c Codec) New(conn net.Conn) ApiCoder {
+	switch c {
+	case JSON:
+		return NewJsonCoder(conn)
+	}
+
+	panic("codec: request for codec #" + strconv.Itoa(int(c)) + " is unavailable")
+}

+ 75 - 0
rpc/codec/json.go

@@ -0,0 +1,75 @@
+package codec
+
+import (
+	"encoding/json"
+	"net"
+
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+const (
+	MAX_RESPONSE_SIZE = 64 * 1024
+)
+
+// Json serialization support
+type JsonCodec struct {
+	c net.Conn
+	d *json.Decoder
+	e *json.Encoder
+}
+
+// Create new JSON coder instance
+func NewJsonCoder(conn net.Conn) ApiCoder {
+	return &JsonCodec{
+		c: conn,
+		d: json.NewDecoder(conn),
+		e: json.NewEncoder(conn),
+	}
+}
+
+// Serialize obj to JSON and write it to conn
+func (self *JsonCodec) ReadRequest() (*shared.Request, error) {
+	req := shared.Request{}
+	err := self.d.Decode(&req)
+	if err == nil {
+		return &req, nil
+	}
+	return nil, err
+}
+
+func (self *JsonCodec) ReadResponse() (interface{}, error) {
+	var err error
+	buf := make([]byte, MAX_RESPONSE_SIZE)
+	n, _ := self.c.Read(buf)
+
+	var failure shared.ErrorResponse
+	if err = json.Unmarshal(buf[:n], &failure); err == nil && failure.Error != nil {
+		return failure, nil
+	}
+
+	var success shared.SuccessResponse
+	if err = json.Unmarshal(buf[:n], &success); err == nil {
+		return success, nil
+	}
+
+	return nil, err
+}
+
+// Encode response to encoded form in underlying stream
+func (self *JsonCodec) Decode(data []byte, msg interface{}) error {
+	return json.Unmarshal(data, msg)
+}
+
+func (self *JsonCodec) Encode(msg interface{}) ([]byte, error) {
+	return json.Marshal(msg)
+}
+
+// Parse JSON data from conn to obj
+func (self *JsonCodec) WriteResponse(res interface{}) error {
+	return self.e.Encode(&res)
+}
+
+// Close decoder and encoder
+func (self *JsonCodec) Close() {
+	self.c.Close()
+}

+ 7 - 0
rpc/comms/comms.go

@@ -0,0 +1,7 @@
+package comms
+
+type EthereumClient interface {
+	Close()
+	Send(interface{}) error
+	Recv() (interface{}, error)
+}

+ 37 - 0
rpc/comms/ipc.go

@@ -0,0 +1,37 @@
+package comms
+
+import (
+	"github.com/ethereum/go-ethereum/rpc/api"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+)
+
+type IpcConfig struct {
+	Endpoint string
+}
+
+type ipcClient struct {
+	c codec.ApiCoder
+}
+
+func (self *ipcClient) Close() {
+	self.c.Close()
+}
+
+func (self *ipcClient) Send(req interface{}) error {
+	return self.c.WriteResponse(req)
+}
+
+func (self *ipcClient) Recv() (interface{}, error) {
+	return self.c.ReadResponse()
+}
+
+// Create a new IPC client, UNIX domain socket on posix, named pipe on Windows
+func NewIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
+	return newIpcClient(cfg, codec)
+}
+
+// Start IPC server
+func StartIpc(cfg IpcConfig, codec codec.Codec, apis ...api.EthereumApi) error {
+	offeredApi := api.Merge(apis...)
+	return startIpc(cfg, codec, offeredApi)
+}

+ 77 - 0
rpc/comms/ipc_unix.go

@@ -0,0 +1,77 @@
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package comms
+
+import (
+	"io"
+	"net"
+	"os"
+
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/rpc/api"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
+	c, err := net.DialUnix("unix", nil, &net.UnixAddr{cfg.Endpoint, "unix"})
+	if err != nil {
+		return nil, err
+	}
+
+	return &ipcClient{codec.New(c)}, nil
+}
+
+func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error {
+	os.Remove(cfg.Endpoint) // in case it still exists from a previous run
+
+	l, err := net.ListenUnix("unix", &net.UnixAddr{Name: cfg.Endpoint, Net: "unix"})
+	if err != nil {
+		return err
+	}
+	os.Chmod(cfg.Endpoint, 0600)
+
+	go func() {
+		for {
+			conn, err := l.AcceptUnix()
+			if err != nil {
+				glog.V(logger.Error).Infof("Error accepting ipc connection - %v\n", err)
+				continue
+			}
+
+			go func(conn net.Conn) {
+				codec := codec.New(conn)
+
+				for {
+					req, err := codec.ReadRequest()
+					if err == io.EOF {
+						codec.Close()
+						return
+					} else if err != nil {
+						glog.V(logger.Error).Infof("IPC recv err - %v\n", err)
+						codec.Close()
+						return
+					}
+
+					var rpcResponse interface{}
+					res, err := api.Execute(req)
+
+					rpcResponse = shared.NewRpcResponse(req.Id, req.Jsonrpc, res, err)
+					err = codec.WriteResponse(rpcResponse)
+					if err != nil {
+						glog.V(logger.Error).Infof("IPC send err - %v\n", err)
+						codec.Close()
+						return
+					}
+				}
+			}(conn)
+		}
+
+		os.Remove(cfg.Endpoint)
+	}()
+
+	glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint)
+
+	return nil
+}

+ 696 - 0
rpc/comms/ipc_windows.go

@@ -0,0 +1,696 @@
+// +build windows
+
+package comms
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"sync"
+	"syscall"
+	"time"
+	"unsafe"
+
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/rpc/api"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+var (
+	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
+
+	procCreateNamedPipeW    = modkernel32.NewProc("CreateNamedPipeW")
+	procConnectNamedPipe    = modkernel32.NewProc("ConnectNamedPipe")
+	procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
+	procWaitNamedPipeW      = modkernel32.NewProc("WaitNamedPipeW")
+	procCreateEventW        = modkernel32.NewProc("CreateEventW")
+	procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult")
+	procCancelIoEx          = modkernel32.NewProc("CancelIoEx")
+)
+
+func createNamedPipe(name *uint16, openMode uint32, pipeMode uint32, maxInstances uint32, outBufSize uint32, inBufSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
+	r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(openMode), uintptr(pipeMode), uintptr(maxInstances), uintptr(outBufSize), uintptr(inBufSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
+	handle = syscall.Handle(r0)
+	if handle == syscall.InvalidHandle {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func cancelIoEx(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) {
+	r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func connectNamedPipe(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) {
+	r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func disconnectNamedPipe(handle syscall.Handle) (err error) {
+	r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(handle), 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func waitNamedPipe(name *uint16, timeout uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
+	var _p0 uint32
+	if manualReset {
+		_p0 = 1
+	} else {
+		_p0 = 0
+	}
+	var _p1 uint32
+	if initialState {
+		_p1 = 1
+	} else {
+		_p1 = 0
+	}
+	r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(sa)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0)
+	handle = syscall.Handle(r0)
+	if handle == syscall.InvalidHandle {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+func getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, transferred *uint32, wait bool) (err error) {
+	var _p0 uint32
+	if wait {
+		_p0 = 1
+	} else {
+		_p0 = 0
+	}
+	r1, _, e1 := syscall.Syscall6(procGetOverlappedResult.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(transferred)), uintptr(_p0), 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = error(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
+const (
+	// openMode
+	pipe_access_duplex   = 0x3
+	pipe_access_inbound  = 0x1
+	pipe_access_outbound = 0x2
+
+	// openMode write flags
+	file_flag_first_pipe_instance = 0x00080000
+	file_flag_write_through       = 0x80000000
+	file_flag_overlapped          = 0x40000000
+
+	// openMode ACL flags
+	write_dac              = 0x00040000
+	write_owner            = 0x00080000
+	access_system_security = 0x01000000
+
+	// pipeMode
+	pipe_type_byte    = 0x0
+	pipe_type_message = 0x4
+
+	// pipeMode read mode flags
+	pipe_readmode_byte    = 0x0
+	pipe_readmode_message = 0x2
+
+	// pipeMode wait mode flags
+	pipe_wait   = 0x0
+	pipe_nowait = 0x1
+
+	// pipeMode remote-client mode flags
+	pipe_accept_remote_clients = 0x0
+	pipe_reject_remote_clients = 0x8
+
+	pipe_unlimited_instances = 255
+
+	nmpwait_wait_forever = 0xFFFFFFFF
+
+	// the two not-an-errors below occur if a client connects to the pipe between
+	// the server's CreateNamedPipe and ConnectNamedPipe calls.
+	error_no_data        syscall.Errno = 0xE8
+	error_pipe_connected syscall.Errno = 0x217
+	error_pipe_busy      syscall.Errno = 0xE7
+	error_sem_timeout    syscall.Errno = 0x79
+
+	error_bad_pathname syscall.Errno = 0xA1
+	error_invalid_name syscall.Errno = 0x7B
+
+	error_io_incomplete syscall.Errno = 0x3e4
+)
+
+var _ net.Conn = (*PipeConn)(nil)
+var _ net.Listener = (*PipeListener)(nil)
+
+// ErrClosed is the error returned by PipeListener.Accept when Close is called
+// on the PipeListener.
+var ErrClosed = PipeError{"Pipe has been closed.", false}
+
+// PipeError is an error related to a call to a pipe
+type PipeError struct {
+	msg     string
+	timeout bool
+}
+
+// Error implements the error interface
+func (e PipeError) Error() string {
+	return e.msg
+}
+
+// Timeout implements net.AddrError.Timeout()
+func (e PipeError) Timeout() bool {
+	return e.timeout
+}
+
+// Temporary implements net.AddrError.Temporary()
+func (e PipeError) Temporary() bool {
+	return false
+}
+
+// Dial connects to a named pipe with the given address. If the specified pipe is not available,
+// it will wait indefinitely for the pipe to become available.
+//
+// The address must be of the form \\.\\pipe\<name> for local pipes and \\<computer>\pipe\<name>
+// for remote pipes.
+//
+// Dial will return a PipeError if you pass in a badly formatted pipe name.
+//
+// Examples:
+//   // local pipe
+//   conn, err := Dial(`\\.\pipe\mypipename`)
+//
+//   // remote pipe
+//   conn, err := Dial(`\\othercomp\pipe\mypipename`)
+func Dial(address string) (*PipeConn, error) {
+	for {
+		conn, err := dial(address, nmpwait_wait_forever)
+		if err == nil {
+			return conn, nil
+		}
+		if isPipeNotReady(err) {
+			<-time.After(100 * time.Millisecond)
+			continue
+		}
+		return nil, err
+	}
+}
+
+// DialTimeout acts like Dial, but will time out after the duration of timeout
+func DialTimeout(address string, timeout time.Duration) (*PipeConn, error) {
+	deadline := time.Now().Add(timeout)
+
+	now := time.Now()
+	for now.Before(deadline) {
+		millis := uint32(deadline.Sub(now) / time.Millisecond)
+		conn, err := dial(address, millis)
+		if err == nil {
+			return conn, nil
+		}
+		if err == error_sem_timeout {
+			// This is WaitNamedPipe's timeout error, so we know we're done
+			return nil, PipeError{fmt.Sprintf(
+				"Timed out waiting for pipe '%s' to come available", address), true}
+		}
+		if isPipeNotReady(err) {
+			left := deadline.Sub(time.Now())
+			retry := 100 * time.Millisecond
+			if left > retry {
+				<-time.After(retry)
+			} else {
+				<-time.After(left - time.Millisecond)
+			}
+			now = time.Now()
+			continue
+		}
+		return nil, err
+	}
+	return nil, PipeError{fmt.Sprintf(
+		"Timed out waiting for pipe '%s' to come available", address), true}
+}
+
+// isPipeNotReady checks the error to see if it indicates the pipe is not ready
+func isPipeNotReady(err error) bool {
+	// Pipe Busy means another client just grabbed the open pipe end,
+	// and the server hasn't made a new one yet.
+	// File Not Found means the server hasn't created the pipe yet.
+	// Neither is a fatal error.
+
+	return err == syscall.ERROR_FILE_NOT_FOUND || err == error_pipe_busy
+}
+
+// newOverlapped creates a structure used to track asynchronous
+// I/O requests that have been issued.
+func newOverlapped() (*syscall.Overlapped, error) {
+	event, err := createEvent(nil, true, true, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &syscall.Overlapped{HEvent: event}, nil
+}
+
+// waitForCompletion waits for an asynchronous I/O request referred to by overlapped to complete.
+// This function returns the number of bytes transferred by the operation and an error code if
+// applicable (nil otherwise).
+func waitForCompletion(handle syscall.Handle, overlapped *syscall.Overlapped) (uint32, error) {
+	_, err := syscall.WaitForSingleObject(overlapped.HEvent, syscall.INFINITE)
+	if err != nil {
+		return 0, err
+	}
+	var transferred uint32
+	err = getOverlappedResult(handle, overlapped, &transferred, true)
+	return transferred, err
+}
+
+// dial is a helper to initiate a connection to a named pipe that has been started by a server.
+// The timeout is only enforced if the pipe server has already created the pipe, otherwise
+// this function will return immediately.
+func dial(address string, timeout uint32) (*PipeConn, error) {
+	name, err := syscall.UTF16PtrFromString(string(address))
+	if err != nil {
+		return nil, err
+	}
+	// If at least one instance of the pipe has been created, this function
+	// will wait timeout milliseconds for it to become available.
+	// It will return immediately regardless of timeout, if no instances
+	// of the named pipe have been created yet.
+	// If this returns with no error, there is a pipe available.
+	if err := waitNamedPipe(name, timeout); err != nil {
+		if err == error_bad_pathname {
+			// badly formatted pipe name
+			return nil, badAddr(address)
+		}
+		return nil, err
+	}
+	pathp, err := syscall.UTF16PtrFromString(address)
+	if err != nil {
+		return nil, err
+	}
+	handle, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE,
+		uint32(syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE), nil, syscall.OPEN_EXISTING,
+		syscall.FILE_FLAG_OVERLAPPED, 0)
+	if err != nil {
+		return nil, err
+	}
+	return &PipeConn{handle: handle, addr: PipeAddr(address)}, nil
+}
+
+// Listen returns a new PipeListener that will listen on a pipe with the given
+// address. The address must be of the form \\.\pipe\<name>
+//
+// Listen will return a PipeError for an incorrectly formatted pipe name.
+func Listen(address string) (*PipeListener, error) {
+	handle, err := createPipe(address, true)
+	if err == error_invalid_name {
+		return nil, badAddr(address)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return &PipeListener{
+		addr:   PipeAddr(address),
+		handle: handle,
+	}, nil
+}
+
+// PipeListener is a named pipe listener. Clients should typically
+// use variables of type net.Listener instead of assuming named pipe.
+type PipeListener struct {
+	addr   PipeAddr
+	handle syscall.Handle
+	closed bool
+
+	// acceptHandle contains the current handle waiting for
+	// an incoming connection or nil.
+	acceptHandle syscall.Handle
+	// acceptOverlapped is set before waiting on a connection.
+	// If not waiting, it is nil.
+	acceptOverlapped *syscall.Overlapped
+	// acceptMutex protects the handle and overlapped structure.
+	acceptMutex sync.Mutex
+}
+
+// Accept implements the Accept method in the net.Listener interface; it
+// waits for the next call and returns a generic net.Conn.
+func (l *PipeListener) Accept() (net.Conn, error) {
+	c, err := l.AcceptPipe()
+	for err == error_no_data {
+		// Ignore clients that connect and immediately disconnect.
+		c, err = l.AcceptPipe()
+	}
+	if err != nil {
+		return nil, err
+	}
+	return c, nil
+}
+
+// AcceptPipe accepts the next incoming call and returns the new connection.
+// It might return an error if a client connected and immediately cancelled
+// the connection.
+func (l *PipeListener) AcceptPipe() (*PipeConn, error) {
+	if l == nil || l.addr == "" || l.closed {
+		return nil, syscall.EINVAL
+	}
+
+	// the first time we call accept, the handle will have been created by the Listen
+	// call. This is to prevent race conditions where the client thinks the server
+	// isn't listening because it hasn't actually called create yet. After the first time, we'll
+	// have to create a new handle each time
+	handle := l.handle
+	if handle == 0 {
+		var err error
+		handle, err = createPipe(string(l.addr), false)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		l.handle = 0
+	}
+
+	overlapped, err := newOverlapped()
+	if err != nil {
+		return nil, err
+	}
+	defer syscall.CloseHandle(overlapped.HEvent)
+	if err := connectNamedPipe(handle, overlapped); err != nil && err != error_pipe_connected {
+		if err == error_io_incomplete || err == syscall.ERROR_IO_PENDING {
+			l.acceptMutex.Lock()
+			l.acceptOverlapped = overlapped
+			l.acceptHandle = handle
+			l.acceptMutex.Unlock()
+			defer func() {
+				l.acceptMutex.Lock()
+				l.acceptOverlapped = nil
+				l.acceptHandle = 0
+				l.acceptMutex.Unlock()
+			}()
+
+			_, err = waitForCompletion(handle, overlapped)
+		}
+		if err == syscall.ERROR_OPERATION_ABORTED {
+			// Return error compatible to net.Listener.Accept() in case the
+			// listener was closed.
+			return nil, ErrClosed
+		}
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &PipeConn{handle: handle, addr: l.addr}, nil
+}
+
+// Close stops listening on the address.
+// Already Accepted connections are not closed.
+func (l *PipeListener) Close() error {
+	if l.closed {
+		return nil
+	}
+	l.closed = true
+	if l.handle != 0 {
+		err := disconnectNamedPipe(l.handle)
+		if err != nil {
+			return err
+		}
+		err = syscall.CloseHandle(l.handle)
+		if err != nil {
+			return err
+		}
+		l.handle = 0
+	}
+	l.acceptMutex.Lock()
+	defer l.acceptMutex.Unlock()
+	if l.acceptOverlapped != nil && l.acceptHandle != 0 {
+		// Cancel the pending IO. This call does not block, so it is safe
+		// to hold onto the mutex above.
+		if err := cancelIoEx(l.acceptHandle, l.acceptOverlapped); err != nil {
+			return err
+		}
+		err := syscall.CloseHandle(l.acceptOverlapped.HEvent)
+		if err != nil {
+			return err
+		}
+		l.acceptOverlapped.HEvent = 0
+		err = syscall.CloseHandle(l.acceptHandle)
+		if err != nil {
+			return err
+		}
+		l.acceptHandle = 0
+	}
+	return nil
+}
+
+// Addr returns the listener's network address, a PipeAddr.
+func (l *PipeListener) Addr() net.Addr { return l.addr }
+
+// PipeConn is the implementation of the net.Conn interface for named pipe connections.
+type PipeConn struct {
+	handle syscall.Handle
+	addr   PipeAddr
+
+	// these aren't actually used yet
+	readDeadline  *time.Time
+	writeDeadline *time.Time
+}
+
+type iodata struct {
+	n   uint32
+	err error
+}
+
+// completeRequest looks at iodata to see if a request is pending. If so, it waits for it to either complete or to
+// abort due to hitting the specified deadline. Deadline may be set to nil to wait forever. If no request is pending,
+// the content of iodata is returned.
+func (c *PipeConn) completeRequest(data iodata, deadline *time.Time, overlapped *syscall.Overlapped) (int, error) {
+	if data.err == error_io_incomplete || data.err == syscall.ERROR_IO_PENDING {
+		var timer <-chan time.Time
+		if deadline != nil {
+			if timeDiff := deadline.Sub(time.Now()); timeDiff > 0 {
+				timer = time.After(timeDiff)
+			}
+		}
+		done := make(chan iodata)
+		go func() {
+			n, err := waitForCompletion(c.handle, overlapped)
+			done <- iodata{n, err}
+		}()
+		select {
+		case data = <-done:
+		case <-timer:
+			syscall.CancelIoEx(c.handle, overlapped)
+			data = iodata{0, timeout(c.addr.String())}
+		}
+	}
+	// Windows will produce ERROR_BROKEN_PIPE upon closing
+	// a handle on the other end of a connection. Go RPC
+	// expects an io.EOF error in this case.
+	if data.err == syscall.ERROR_BROKEN_PIPE {
+		data.err = io.EOF
+	}
+	return int(data.n), data.err
+}
+
+// Read implements the net.Conn Read method.
+func (c *PipeConn) Read(b []byte) (int, error) {
+	// Use ReadFile() rather than Read() because the latter
+	// contains a workaround that eats ERROR_BROKEN_PIPE.
+	overlapped, err := newOverlapped()
+	if err != nil {
+		return 0, err
+	}
+	defer syscall.CloseHandle(overlapped.HEvent)
+	var n uint32
+	err = syscall.ReadFile(c.handle, b, &n, overlapped)
+	return c.completeRequest(iodata{n, err}, c.readDeadline, overlapped)
+}
+
+// Write implements the net.Conn Write method.
+func (c *PipeConn) Write(b []byte) (int, error) {
+	overlapped, err := newOverlapped()
+	if err != nil {
+		return 0, err
+	}
+	defer syscall.CloseHandle(overlapped.HEvent)
+	var n uint32
+	err = syscall.WriteFile(c.handle, b, &n, overlapped)
+	return c.completeRequest(iodata{n, err}, c.writeDeadline, overlapped)
+}
+
+// Close closes the connection.
+func (c *PipeConn) Close() error {
+	return syscall.CloseHandle(c.handle)
+}
+
+// LocalAddr returns the local network address.
+func (c *PipeConn) LocalAddr() net.Addr {
+	return c.addr
+}
+
+// RemoteAddr returns the remote network address.
+func (c *PipeConn) RemoteAddr() net.Addr {
+	// not sure what to do here, we don't have remote addr....
+	return c.addr
+}
+
+// SetDeadline implements the net.Conn SetDeadline method.
+// Note that timeouts are only supported on Windows Vista/Server 2008 and above
+func (c *PipeConn) SetDeadline(t time.Time) error {
+	c.SetReadDeadline(t)
+	c.SetWriteDeadline(t)
+	return nil
+}
+
+// SetReadDeadline implements the net.Conn SetReadDeadline method.
+// Note that timeouts are only supported on Windows Vista/Server 2008 and above
+func (c *PipeConn) SetReadDeadline(t time.Time) error {
+	c.readDeadline = &t
+	return nil
+}
+
+// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
+// Note that timeouts are only supported on Windows Vista/Server 2008 and above
+func (c *PipeConn) SetWriteDeadline(t time.Time) error {
+	c.writeDeadline = &t
+	return nil
+}
+
+// PipeAddr represents the address of a named pipe.
+type PipeAddr string
+
+// Network returns the address's network name, "pipe".
+func (a PipeAddr) Network() string { return "pipe" }
+
+// String returns the address of the pipe
+func (a PipeAddr) String() string {
+	return string(a)
+}
+
+// createPipe is a helper function to make sure we always create pipes
+// with the same arguments, since subsequent calls to create pipe need
+// to use the same arguments as the first one. If first is set, fail
+// if the pipe already exists.
+func createPipe(address string, first bool) (syscall.Handle, error) {
+	n, err := syscall.UTF16PtrFromString(address)
+	if err != nil {
+		return 0, err
+	}
+	mode := uint32(pipe_access_duplex | syscall.FILE_FLAG_OVERLAPPED)
+	if first {
+		mode |= file_flag_first_pipe_instance
+	}
+	return createNamedPipe(n,
+		mode,
+		pipe_type_byte,
+		pipe_unlimited_instances,
+		512, 512, 0, nil)
+}
+
+func badAddr(addr string) PipeError {
+	return PipeError{fmt.Sprintf("Invalid pipe address '%s'.", addr), false}
+}
+func timeout(addr string) PipeError {
+	return PipeError{fmt.Sprintf("Pipe IO timed out waiting for '%s'", addr), true}
+}
+
+func newIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
+	c, err := Dial(cfg.Endpoint)
+	if err != nil {
+		return nil, err
+	}
+
+	return &ipcClient{codec.New(c)}, nil
+}
+
+func startIpc(cfg IpcConfig, codec codec.Codec, api api.EthereumApi) error {
+	os.Remove(cfg.Endpoint) // in case it still exists from a previous run
+
+	l, err := Listen(cfg.Endpoint)
+	if err != nil {
+		return err
+	}
+	os.Chmod(cfg.Endpoint, 0600)
+
+	go func() {
+		for {
+			conn, err := l.Accept()
+			if err != nil {
+				glog.V(logger.Error).Infof("Error accepting ipc connection - %v\n", err)
+				continue
+			}
+
+			go func(conn net.Conn) {
+				codec := codec.New(conn)
+
+				for {
+					req, err := codec.ReadRequest()
+					if err == io.EOF {
+						codec.Close()
+						return
+					} else if err != nil {
+						glog.V(logger.Error).Infof("IPC recv err - %v\n", err)
+						codec.Close()
+						return
+					}
+
+					var rpcResponse interface{}
+					res, err := api.Execute(req)
+
+					rpcResponse = shared.NewRpcResponse(req.Id, req.Jsonrpc, res, err)
+					err = codec.WriteResponse(rpcResponse)
+					if err != nil {
+						glog.V(logger.Error).Infof("IPC send err - %v\n", err)
+						codec.Close()
+						return
+					}
+				}
+			}(conn)
+		}
+	}()
+
+	glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint)
+
+	return nil
+}

+ 92 - 4
rpc/jeth.go

@@ -4,17 +4,23 @@ import (
 	"encoding/json"
 	"fmt"
 
+	"reflect"
+
 	"github.com/ethereum/go-ethereum/jsre"
+	"github.com/ethereum/go-ethereum/rpc/codec"
+	"github.com/ethereum/go-ethereum/rpc/comms"
+	"github.com/ethereum/go-ethereum/rpc/shared"
 	"github.com/robertkrimen/otto"
 )
 
 type Jeth struct {
-	ethApi *EthereumApi
-	re     *jsre.JSRE
+	ethApi  *EthereumApi
+	re      *jsre.JSRE
+	ipcpath string
 }
 
-func NewJeth(ethApi *EthereumApi, re *jsre.JSRE) *Jeth {
-	return &Jeth{ethApi, re}
+func NewJeth(ethApi *EthereumApi, re *jsre.JSRE, ipcpath string) *Jeth {
+	return &Jeth{ethApi, re, ipcpath}
 }
 
 func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
@@ -81,3 +87,85 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
 
 	return
 }
+
+func (self *Jeth) SendIpc(call otto.FunctionCall) (response otto.Value) {
+	reqif, err := call.Argument(0).Export()
+	if err != nil {
+		return self.err(call, -32700, err.Error(), nil)
+	}
+
+	client, err := comms.NewIpcClient(comms.IpcConfig{self.ipcpath}, codec.JSON)
+	if err != nil {
+		fmt.Println("Unable to connect to geth.")
+		return self.err(call, -32603, err.Error(), -1)
+	}
+	defer client.Close()
+
+	jsonreq, err := json.Marshal(reqif)
+	var reqs []RpcRequest
+	batch := true
+	err = json.Unmarshal(jsonreq, &reqs)
+	if err != nil {
+		reqs = make([]RpcRequest, 1)
+		err = json.Unmarshal(jsonreq, &reqs[0])
+		batch = false
+	}
+
+	call.Otto.Set("response_len", len(reqs))
+	call.Otto.Run("var ret_response = new Array(response_len);")
+
+	for i, req := range reqs {
+		err := client.Send(&req)
+		if err != nil {
+			fmt.Println("Error send request:", err)
+			return self.err(call, -32603, err.Error(), req.Id)
+		}
+
+		respif, err := client.Recv()
+		if err != nil {
+			fmt.Println("Error recv response:", err)
+			return self.err(call, -32603, err.Error(), req.Id)
+		}
+
+		if res, ok := respif.(shared.SuccessResponse); ok {
+			call.Otto.Set("ret_id", res.Id)
+			call.Otto.Set("ret_jsonrpc", res.Jsonrpc)
+			resObj, _ := json.Marshal(res.Result)
+			call.Otto.Set("ret_result", string(resObj))
+			call.Otto.Set("response_idx", i)
+
+			response, err = call.Otto.Run(`
+				ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
+			`)
+		} else if res, ok := respif.(shared.ErrorResponse); ok {
+			fmt.Printf("Error: %s (%d)\n", res.Error.Message, res.Error.Code)
+
+			call.Otto.Set("ret_id", res.Id)
+			call.Otto.Set("ret_jsonrpc", res.Jsonrpc)
+			call.Otto.Set("ret_error", res.Error)
+			call.Otto.Set("response_idx", i)
+
+			response, _ = call.Otto.Run(`
+				ret_response = { jsonrpc: ret_jsonrpc, id: ret_id, error: ret_error };
+			`)
+			return
+		} else {
+			fmt.Printf("unexpected response\n", reflect.TypeOf(respif))
+		}
+	}
+
+	if !batch {
+		call.Otto.Run("ret_response = ret_response[0];")
+	}
+
+	if call.Argument(1).IsObject() {
+		call.Otto.Set("callback", call.Argument(1))
+		call.Otto.Run(`
+	    if (Object.prototype.toString.call(callback) == '[object Function]') {
+			callback(null, ret_response);
+		}
+		`)
+	}
+
+	return
+}

+ 96 - 0
rpc/shared/errors.go

@@ -0,0 +1,96 @@
+package shared
+
+import "fmt"
+
+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 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 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,
+	}
+}

+ 64 - 0
rpc/shared/types.go

@@ -0,0 +1,64 @@
+package shared
+
+import (
+	"encoding/json"
+
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+)
+
+// RPC request
+type Request struct {
+	Id      interface{}     `json:"id"`
+	Jsonrpc string          `json:"jsonrpc"`
+	Method  string          `json:"method"`
+	Params  json.RawMessage `json:"params"`
+}
+
+// RPC response
+type Response struct {
+	Id      interface{} `json:"id"`
+	Jsonrpc string      `json:"jsonrpc"`
+}
+
+// RPC success response
+type SuccessResponse struct {
+	Id      interface{} `json:"id"`
+	Jsonrpc string      `json:"jsonrpc"`
+	Result  interface{} `json:"result"`
+}
+
+// RPC error response
+type ErrorResponse struct {
+	Id      interface{}  `json:"id"`
+	Jsonrpc string       `json:"jsonrpc"`
+	Error   *ErrorObject `json:"error"`
+}
+
+// RPC error response details
+type ErrorObject struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+	// Data    interface{} `json:"data"`
+}
+
+func NewRpcResponse(id interface{}, jsonrpcver string, reply interface{}, err error) *interface{} {
+	var response interface{}
+
+	switch err.(type) {
+	case nil:
+		response = &SuccessResponse{Jsonrpc: jsonrpcver, Id: id, Result: reply}
+	case *NotImplementedError:
+		jsonerr := &ErrorObject{-32601, err.Error()}
+		response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr}
+	case *DecodeParamError, *InsufficientParamsError, *ValidationError, *InvalidTypeError:
+		jsonerr := &ErrorObject{-32602, err.Error()}
+		response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr}
+	default:
+		jsonerr := &ErrorObject{-32603, err.Error()}
+		response = &ErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: jsonerr}
+	}
+
+	glog.V(logger.Detail).Infof("Generated response: %T %s", response, response)
+	return &response
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません