mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
Split RPC API into multiple seperate smaller API's which can be offered over different RPC mechanisms
This commit is contained in:
parent
5b14fdb94b
commit
0eca879627
20 changed files with 2453 additions and 8 deletions
6
cmd/console/contracts.go
Normal file
6
cmd/console/contracts.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package main
|
||||
|
||||
var (
|
||||
globalRegistrar = `var GlobalRegistrar = web3.eth.contract([{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"name","outputs":[{"name":"o_name","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"content","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"addr","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"subRegistrar","outputs":[{"name":"o_subRegistrar","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_registrar","type":"address"}],"name":"setSubRegistrar","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"Registrar","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_a","type":"address"},{"name":"_primary","type":"bool"}],"name":"setAddress","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_content","type":"bytes32"}],"name":"setContent","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"disown","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"address"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"}],"name":"Changed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"addr","type":"address"}],"name":"PrimaryChanged","type":"event"}]);`
|
||||
globalRegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
|
||||
)
|
||||
275
cmd/console/js.go
Normal file
275
cmd/console/js.go
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
// 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"
|
||||
|
||||
"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/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
|
||||
}
|
||||
|
||||
func newJSRE(datadir, libPath string) *jsre {
|
||||
js := &jsre{ps1: "> "}
|
||||
js.wait = make(chan *big.Int)
|
||||
|
||||
// update state in separare forever blocks
|
||||
js.re = re.New(libPath)
|
||||
js.apiBindings()
|
||||
// js.adminBindings()
|
||||
|
||||
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)
|
||||
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() {
|
||||
ethApi := rpc.NewEthereumApi(nil)
|
||||
jeth := rpc.NewJeth(ethApi, js.re)
|
||||
|
||||
js.re.Set("jeth", struct{}{})
|
||||
t, _ := js.re.Get("jeth")
|
||||
jethObj := t.Object()
|
||||
jethObj.Set("send", jeth.Send)
|
||||
jethObj.Set("sendAsync", jeth.Send)
|
||||
|
||||
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.Ethereum_JS)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error loading ethereum.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)
|
||||
}
|
||||
_, err = js.re.Eval(`
|
||||
var eth = web3.eth;
|
||||
var shh = web3.shh;
|
||||
var db = web3.db;
|
||||
var net = web3.net;
|
||||
`)
|
||||
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) 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)
|
||||
}
|
||||
}
|
||||
48
cmd/console/main.go
Normal file
48
cmd/console/main.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
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 (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientIdentifier = "Geth console"
|
||||
Version = "0.9.26"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// TODO, datadir + jspath
|
||||
repl := newJSRE("/home/bas/.ethereum", ".")
|
||||
repl.interactive()
|
||||
}
|
||||
|
|
@ -238,6 +238,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
|||
utils.RPCEnabledFlag,
|
||||
utils.RPCListenAddrFlag,
|
||||
utils.RPCPortFlag,
|
||||
utils.IPCEnabledFlag,
|
||||
utils.WhisperEnabledFlag,
|
||||
utils.VMDebugFlag,
|
||||
utils.ProtocolVersionFlag,
|
||||
|
|
@ -386,6 +387,11 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
|
|||
utils.Fatalf("Error starting RPC: %v", err)
|
||||
}
|
||||
}
|
||||
if ctx.GlobalBool(utils.IPCEnabledFlag.Name) {
|
||||
if err := utils.StartIPC(eth, ctx); err != nil {
|
||||
utils.Fatalf("Error starting IPC: %v", err)
|
||||
}
|
||||
}
|
||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
|
||||
if err := eth.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name)); err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
|
|
|
|||
|
|
@ -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/comms"
|
||||
"github.com/ethereum/go-ethereum/rpc/api"
|
||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -201,6 +204,10 @@ var (
|
|||
Usage: "Domain on which to send Access-Control-Allow-Origin header",
|
||||
Value: "",
|
||||
}
|
||||
IPCEnabledFlag = cli.BoolFlag{
|
||||
Name: "ipc",
|
||||
Usage: "Enable the IPC-RPC server",
|
||||
}
|
||||
// Network Settings
|
||||
MaxPeersFlag = cli.IntFlag{
|
||||
Name: "maxpeers",
|
||||
|
|
@ -369,6 +376,21 @@ func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error {
|
|||
return rpc.Start(xeth, config)
|
||||
}
|
||||
|
||||
func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error {
|
||||
config := comms.IpcConfig{
|
||||
Endpoint: ctx.GlobalString(DataDirFlag.Name) + "/geth.sock",
|
||||
}
|
||||
|
||||
xeth := xeth.New(eth, nil)
|
||||
// offered API over IPC
|
||||
codec := codec.JSON
|
||||
web3Api := api.NewWeb3(xeth, codec)
|
||||
ethApi := api.NewEth(xeth, codec)
|
||||
netApi := api.NewNet(xeth, codec)
|
||||
|
||||
return comms.StartIpc(config, codec, web3Api, ethApi, netApi)
|
||||
}
|
||||
|
||||
func StartPProf(ctx *cli.Context) {
|
||||
address := fmt.Sprintf("localhost:%d", ctx.GlobalInt(PProfPortFlag.Name))
|
||||
go func() {
|
||||
|
|
|
|||
44
rpc/api/api.go
Normal file
44
rpc/api/api.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||
)
|
||||
|
||||
// Descriptor for all API implementations
|
||||
type Ethereum interface {
|
||||
// Execute single API request
|
||||
Execute(*shared.Request) (interface{}, error)
|
||||
// List with supported methods
|
||||
Methods() []string
|
||||
}
|
||||
|
||||
type mergedEthereumApi struct {
|
||||
apis map[string]Ethereum
|
||||
}
|
||||
|
||||
func newMergedEthereumApi(apis ...Ethereum) *mergedEthereumApi {
|
||||
mergedApi := new(mergedEthereumApi)
|
||||
mergedApi.apis = make(map[string]Ethereum)
|
||||
|
||||
for _, api := range apis {
|
||||
for _, method := range api.Methods() {
|
||||
mergedApi.apis[method] = api
|
||||
}
|
||||
}
|
||||
return mergedApi
|
||||
}
|
||||
|
||||
func (self *mergedEthereumApi) Methods() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *mergedEthereumApi) Execute(req *shared.Request) (interface{}, error) {
|
||||
if api, found := self.apis[req.Method]; found {
|
||||
return api.Execute(req)
|
||||
}
|
||||
return nil, shared.NewNotImplementedError(req.Method)
|
||||
}
|
||||
|
||||
func Merge(apis ...Ethereum) Ethereum {
|
||||
return newMergedEthereumApi(apis...)
|
||||
}
|
||||
11
rpc/api/errors.go
Normal file
11
rpc/api/errors.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package api
|
||||
|
||||
import "fmt"
|
||||
|
||||
type NotImplementedError struct {
|
||||
Method string
|
||||
}
|
||||
|
||||
func (e *NotImplementedError) Error() string {
|
||||
return fmt.Sprintf("%s method not implemented", e.Method)
|
||||
}
|
||||
1000
rpc/api/eth.go
Normal file
1000
rpc/api/eth.go
Normal file
File diff suppressed because it is too large
Load diff
69
rpc/api/net.go
Normal file
69
rpc/api/net.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||
"github.com/ethereum/go-ethereum/xeth"
|
||||
)
|
||||
|
||||
var (
|
||||
// mapping between methods and handlers
|
||||
netMapping = map[string]nethandler{
|
||||
"net_version": (*net).Version,
|
||||
"net_peerCount": (*net).PeerCount,
|
||||
"net_listening": (*net).IsListening,
|
||||
}
|
||||
)
|
||||
|
||||
// net callback handler
|
||||
type nethandler func(*net, *shared.Request) (interface{}, error)
|
||||
|
||||
// net api provider
|
||||
type net struct {
|
||||
xeth *xeth.XEth
|
||||
methods map[string]nethandler
|
||||
codec codec.ApiCoder
|
||||
}
|
||||
|
||||
// create a new net api instance
|
||||
func NewNet(xeth *xeth.XEth, coder codec.Codec) *net {
|
||||
return &net{
|
||||
xeth: xeth,
|
||||
methods: netMapping,
|
||||
codec: coder.New(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// collection with supported methods
|
||||
func (self *net) 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 *net) Execute(req *shared.Request) (interface{}, error) {
|
||||
if callback, ok := self.methods[req.Method]; ok {
|
||||
return callback(self, req)
|
||||
}
|
||||
|
||||
return nil, shared.NewNotImplementedError(req.Method)
|
||||
}
|
||||
|
||||
// Network version
|
||||
func (self *net) Version(req *shared.Request) (interface{}, error) {
|
||||
return self.xeth.NetworkVersion(), nil
|
||||
}
|
||||
|
||||
// Number of connected peers
|
||||
func (self *net) PeerCount(req *shared.Request) (interface{}, error) {
|
||||
return newHexNum(self.xeth.PeerCount()), nil
|
||||
}
|
||||
|
||||
func (self *net) IsListening(req *shared.Request) (interface{}, error) {
|
||||
return self.xeth.IsListening(), nil
|
||||
}
|
||||
460
rpc/api/utils.go
Normal file
460
rpc/api/utils.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
80
rpc/api/web3.go
Normal file
80
rpc/api/web3.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
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 (
|
||||
Web3Version = "1.0.0"
|
||||
)
|
||||
|
||||
var (
|
||||
// mapping between methods and handlers
|
||||
Web3Mapping = map[string]web3handler{
|
||||
"web3_sha3": (*web3).Sha3,
|
||||
"web3_clientVersion": (*web3).ClientVersion,
|
||||
}
|
||||
)
|
||||
|
||||
// web3 callback handler
|
||||
type web3handler func(*web3, *shared.Request) (interface{}, error)
|
||||
|
||||
// web3 api provider
|
||||
type web3 struct {
|
||||
xeth *xeth.XEth
|
||||
methods map[string]web3handler
|
||||
codec codec.ApiCoder
|
||||
}
|
||||
|
||||
// create a new web3 api instance
|
||||
func NewWeb3(xeth *xeth.XEth, coder codec.Codec) *web3 {
|
||||
return &web3{
|
||||
xeth: xeth,
|
||||
methods: Web3Mapping,
|
||||
codec: coder.New(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// collection with supported methods
|
||||
func (self *web3) 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 *web3) Execute(req *shared.Request) (interface{}, error) {
|
||||
if callback, ok := self.methods[req.Method]; ok {
|
||||
return callback(self, req)
|
||||
}
|
||||
|
||||
return nil, &NotImplementedError{req.Method}
|
||||
}
|
||||
|
||||
// Version of the API this instance provides
|
||||
func (self *web3) Version() string {
|
||||
return Web3Version
|
||||
}
|
||||
|
||||
// Calculates the sha3 over req.Params.Data
|
||||
func (self *web3) Sha3(req *shared.Request) (interface{}, error) {
|
||||
args := new(shared.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 *web3) ClientVersion(req *shared.Request) (interface{}, error) {
|
||||
return self.xeth.ClientVersion(), nil
|
||||
}
|
||||
47
rpc/codec/codec.go
Normal file
47
rpc/codec/codec.go
Normal file
|
|
@ -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")
|
||||
}
|
||||
68
rpc/codec/json.go
Normal file
68
rpc/codec/json.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package codec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||
)
|
||||
|
||||
// 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
|
||||
var success shared.SuccessResponse
|
||||
if err = self.d.Decode(&success); err == nil {
|
||||
return success, nil
|
||||
}
|
||||
|
||||
var failure shared.ErrorResponse
|
||||
if err = self.d.Decode(&failure); err == nil {
|
||||
return failure, 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
rpc/comms/comms.go
Normal file
7
rpc/comms/comms.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package comms
|
||||
|
||||
type EthereumClient interface {
|
||||
Close()
|
||||
Send(interface{}) error
|
||||
Recv() (interface{}, error)
|
||||
}
|
||||
37
rpc/comms/ipc.go
Normal file
37
rpc/comms/ipc.go
Normal file
|
|
@ -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.Ethereum) error {
|
||||
offered := api.Merge(apis...)
|
||||
return startIpc(cfg, codec, offered)
|
||||
}
|
||||
76
rpc/comms/ipc_unix.go
Normal file
76
rpc/comms/ipc_unix.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||
|
||||
package comms
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"io"
|
||||
|
||||
"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.Ethereum) 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)
|
||||
glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint)
|
||||
|
||||
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)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
43
rpc/jeth.go
43
rpc/jeth.go
|
|
@ -3,8 +3,12 @@ package rpc
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/jsre"
|
||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||
"github.com/ethereum/go-ethereum/rpc/comms"
|
||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
|
|
@ -29,11 +33,21 @@ func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface
|
|||
}
|
||||
|
||||
func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
||||
|
||||
reqif, err := call.Argument(0).Export()
|
||||
|
||||
if err != nil {
|
||||
return self.err(call, -32700, err.Error(), nil)
|
||||
}
|
||||
|
||||
// TODO
|
||||
client, err := comms.NewIpcClient(comms.IpcConfig{"/home/bas/.ethereum/geth.sock"}, codec.JSON)
|
||||
if err != nil {
|
||||
fmt.Println("Error response:", err)
|
||||
return self.err(call, -32603, err.Error(), -1)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
jsonreq, err := json.Marshal(reqif)
|
||||
var reqs []RpcRequest
|
||||
batch := true
|
||||
|
|
@ -48,19 +62,34 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
|||
call.Otto.Run("var ret_response = new Array(response_len);")
|
||||
|
||||
for i, req := range reqs {
|
||||
var respif interface{}
|
||||
err = self.ethApi.GetRequestReply(&req, &respif)
|
||||
err := client.Send(&req)
|
||||
if err != nil {
|
||||
fmt.Println("Error response:", err)
|
||||
fmt.Println("Error send request:", err)
|
||||
return self.err(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
call.Otto.Set("ret_jsonrpc", jsonrpcver)
|
||||
call.Otto.Set("ret_id", req.Id)
|
||||
|
||||
res, _ := json.Marshal(respif)
|
||||
respif, err := client.Recv()
|
||||
if err != nil {
|
||||
fmt.Println("Error recv response:", err)
|
||||
return self.err(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
|
||||
call.Otto.Set("ret_result", string(res))
|
||||
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)
|
||||
} else if res, ok := respif.(shared.ErrorResponse); ok {
|
||||
call.Otto.Set("ret_id", res.Id)
|
||||
call.Otto.Set("ret_jsonrpc", res.Jsonrpc)
|
||||
errorObj, _ := json.Marshal(res.Error)
|
||||
call.Otto.Set("ret_result", string(errorObj))
|
||||
call.Otto.Set("response_idx", i)
|
||||
} else {
|
||||
fmt.Printf("different type\n", reflect.TypeOf(respif))
|
||||
}
|
||||
|
||||
response, err = call.Otto.Run(`
|
||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
||||
`)
|
||||
|
|
|
|||
59
rpc/shared/api.go
Normal file
59
rpc/shared/api.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package shared
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Id interface{} `json:"id"`
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Id interface{} `json:"id"`
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
}
|
||||
|
||||
type SuccessResponse struct {
|
||||
Id interface{} `json:"id"`
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Id interface{} `json:"id"`
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Error *ErrorObject `json:"error"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
5
rpc/shared/apiargs.go
Normal file
5
rpc/shared/apiargs.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package shared
|
||||
|
||||
type Sha3Args struct {
|
||||
Data string
|
||||
}
|
||||
96
rpc/shared/errors.go
Normal file
96
rpc/shared/errors.go
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue