aboutsummaryrefslogtreecommitdiffstats
path: root/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'rpc')
-rw-r--r--rpc/api.go734
-rw-r--r--rpc/api_test.go38
-rw-r--r--rpc/args.go258
-rw-r--r--rpc/http/server.go114
-rw-r--r--rpc/messages.go419
-rw-r--r--rpc/util.go139
-rw-r--r--rpc/ws/server.go121
7 files changed, 1823 insertions, 0 deletions
diff --git a/rpc/api.go b/rpc/api.go
new file mode 100644
index 000000000..28024c206
--- /dev/null
+++ b/rpc/api.go
@@ -0,0 +1,734 @@
+/*
+For each request type, define the following:
+
+1. RpcRequest "To" method [message.go], which does basic validation and conversion to "Args" type via json.Decoder()
+2. json.Decoder() calls "UnmarshalON" defined on each "Args" struct
+3. EthereumApi method, taking the "Args" type and replying with an interface to be marshalled to ON
+
+*/
+package rpc
+
+import (
+ "fmt"
+ "math/big"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/ethutil"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/event/filter"
+ "github.com/ethereum/go-ethereum/state"
+ "github.com/ethereum/go-ethereum/ui"
+ "github.com/ethereum/go-ethereum/xeth"
+)
+
+var (
+ defaultGasPrice = big.NewInt(10000000000000)
+ defaultGas = big.NewInt(10000)
+ filterTickerTime = 15 * time.Second
+)
+
+type EthereumApi struct {
+ eth *xeth.XEth
+ xethMu sync.RWMutex
+ mux *event.TypeMux
+
+ quit chan struct{}
+ filterManager *filter.FilterManager
+
+ logMut sync.RWMutex
+ logs map[int]*logFilter
+
+ messagesMut sync.RWMutex
+ messages map[int]*whisperFilter
+ // Register keeps a list of accounts and transaction data
+ regmut sync.Mutex
+ register map[string][]*NewTxArgs
+
+ db ethutil.Database
+
+ defaultBlockAge int64
+}
+
+func NewEthereumApi(eth *xeth.XEth) *EthereumApi {
+ db, _ := ethdb.NewLDBDatabase("dapps")
+ api := &EthereumApi{
+ eth: eth,
+ mux: eth.Backend().EventMux(),
+ quit: make(chan struct{}),
+ filterManager: filter.NewFilterManager(eth.Backend().EventMux()),
+ logs: make(map[int]*logFilter),
+ messages: make(map[int]*whisperFilter),
+ db: db,
+ defaultBlockAge: -1,
+ }
+ go api.filterManager.Start()
+ go api.start()
+
+ return api
+}
+
+func (self *EthereumApi) setStateByBlockNumber(num int64) {
+ chain := self.xeth().Backend().ChainManager()
+ var block *types.Block
+
+ if self.defaultBlockAge < 0 {
+ num = chain.CurrentBlock().Number().Int64() + num + 1
+ }
+ block = chain.GetBlockByNumber(uint64(num))
+
+ if block != nil {
+ self.useState(state.New(block.Root(), self.xeth().Backend().Db()))
+ } else {
+ self.useState(chain.State())
+ }
+}
+
+func (self *EthereumApi) start() {
+ timer := time.NewTicker(filterTickerTime)
+ events := self.mux.Subscribe(core.ChainEvent{})
+
+done:
+ for {
+ select {
+ case ev := <-events.Chan():
+ switch ev.(type) {
+ case core.ChainEvent:
+ if self.defaultBlockAge < 0 {
+ self.setStateByBlockNumber(self.defaultBlockAge)
+ }
+ }
+ case <-timer.C:
+ self.logMut.Lock()
+ self.messagesMut.Lock()
+ for id, filter := range self.logs {
+ if time.Since(filter.timeout) > 20*time.Second {
+ self.filterManager.UninstallFilter(id)
+ delete(self.logs, id)
+ }
+ }
+
+ for id, filter := range self.messages {
+ if time.Since(filter.timeout) > 20*time.Second {
+ self.xeth().Whisper().Unwatch(id)
+ delete(self.messages, id)
+ }
+ }
+ self.logMut.Unlock()
+ self.messagesMut.Unlock()
+ case <-self.quit:
+ break done
+ }
+ }
+}
+
+func (self *EthereumApi) stop() {
+ close(self.quit)
+}
+
+func (self *EthereumApi) Register(args string, reply *interface{}) error {
+ self.regmut.Lock()
+ defer self.regmut.Unlock()
+
+ if _, ok := self.register[args]; ok {
+ self.register[args] = nil // register with empty
+ }
+ return nil
+}
+
+func (self *EthereumApi) Unregister(args string, reply *interface{}) error {
+ self.regmut.Lock()
+ defer self.regmut.Unlock()
+
+ delete(self.register, args)
+
+ return nil
+}
+
+func (self *EthereumApi) WatchTx(args string, reply *interface{}) error {
+ self.regmut.Lock()
+ defer self.regmut.Unlock()
+
+ txs := self.register[args]
+ self.register[args] = nil
+
+ *reply = txs
+ return nil
+}
+
+func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) error {
+ var id int
+ filter := core.NewFilter(self.xeth().Backend())
+ filter.SetOptions(toFilterOptions(args))
+ filter.LogsCallback = func(logs state.Logs) {
+ self.logMut.Lock()
+ defer self.logMut.Unlock()
+
+ self.logs[id].add(logs...)
+ }
+ id = self.filterManager.InstallFilter(filter)
+ self.logs[id] = &logFilter{timeout: time.Now()}
+
+ *reply = id
+
+ return nil
+}
+
+func (self *EthereumApi) UninstallFilter(id int, reply *interface{}) error {
+ delete(self.logs, id)
+ self.filterManager.UninstallFilter(id)
+ *reply = true
+ return nil
+}
+
+func (self *EthereumApi) NewFilterString(args string, reply *interface{}) error {
+ var id int
+ filter := core.NewFilter(self.xeth().Backend())
+
+ callback := func(block *types.Block) {
+ self.logMut.Lock()
+ defer self.logMut.Unlock()
+
+ self.logs[id].add(&state.StateLog{})
+ }
+ if args == "pending" {
+ filter.PendingCallback = callback
+ } else if args == "chain" {
+ filter.BlockCallback = callback
+ }
+
+ id = self.filterManager.InstallFilter(filter)
+ self.logs[id] = &logFilter{timeout: time.Now()}
+ *reply = id
+
+ return nil
+}
+
+func (self *EthereumApi) FilterChanged(id int, reply *interface{}) error {
+ self.logMut.Lock()
+ defer self.logMut.Unlock()
+
+ if self.logs[id] != nil {
+ *reply = toLogs(self.logs[id].get())
+ }
+
+ return nil
+}
+
+func (self *EthereumApi) Logs(id int, reply *interface{}) error {
+ self.logMut.Lock()
+ defer self.logMut.Unlock()
+
+ filter := self.filterManager.GetFilter(id)
+ if filter != nil {
+ *reply = toLogs(filter.Find())
+ }
+
+ return nil
+}
+
+func (self *EthereumApi) AllLogs(args *FilterOptions, reply *interface{}) error {
+ filter := core.NewFilter(self.xeth().Backend())
+ filter.SetOptions(toFilterOptions(args))
+
+ *reply = toLogs(filter.Find())
+
+ return nil
+}
+
+func (p *EthereumApi) GetBlock(args *GetBlockArgs, reply *interface{}) error {
+ // This seems a bit precarious Maybe worth splitting to discrete functions
+ if len(args.Hash) > 0 {
+ *reply = p.xeth().BlockByHash(args.Hash)
+ } else {
+ *reply = p.xeth().BlockByNumber(args.BlockNumber)
+ }
+ return nil
+}
+
+func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error {
+ if len(args.Gas) == 0 {
+ args.Gas = defaultGas.String()
+ }
+
+ if len(args.GasPrice) == 0 {
+ args.GasPrice = defaultGasPrice.String()
+ }
+
+ // TODO if no_private_key then
+ //if _, exists := p.register[args.From]; exists {
+ // p.register[args.From] = append(p.register[args.From], args)
+ //} else {
+ /*
+ account := accounts.Get(fromHex(args.From))
+ if account != nil {
+ if account.Unlocked() {
+ if !unlockAccount(account) {
+ return
+ }
+ }
+
+ result, _ := account.Transact(fromHex(args.To), fromHex(args.Value), fromHex(args.Gas), fromHex(args.GasPrice), fromHex(args.Data))
+ if len(result) > 0 {
+ *reply = toHex(result)
+ }
+ } else if _, exists := p.register[args.From]; exists {
+ p.register[ags.From] = append(p.register[args.From], args)
+ }
+ */
+ result, _ := p.xeth().Transact( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data)
+ *reply = result
+ //}
+
+ return nil
+}
+
+func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error {
+ result, err := p.xeth().Call( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data)
+ if err != nil {
+ return err
+ }
+
+ *reply = result
+ return nil
+}
+
+func (p *EthereumApi) PushTx(args *PushTxArgs, reply *interface{}) error {
+ err := args.requirementsPushTx()
+ if err != nil {
+ return err
+ }
+ result, _ := p.xeth().PushTx(args.Tx)
+ *reply = result
+ return nil
+}
+
+func (p *EthereumApi) GetStateAt(args *GetStateArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+
+ state := p.xeth().State().SafeGet(args.Address)
+
+ value := state.StorageString(args.Key)
+ var hx string
+ if strings.Index(args.Key, "0x") == 0 {
+ hx = string([]byte(args.Key)[2:])
+ } else {
+ // Convert the incoming string (which is a bigint) into hex
+ i, _ := new(big.Int).SetString(args.Key, 10)
+ hx = ethutil.Bytes2Hex(i.Bytes())
+ }
+ rpclogger.Debugf("GetStateAt(%s, %s)\n", args.Address, hx)
+ *reply = map[string]string{args.Key: value.Str()}
+ return nil
+}
+
+func (p *EthereumApi) GetStorageAt(args *GetStorageArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+
+ *reply = p.xeth().State().SafeGet(args.Address).Storage()
+ return nil
+}
+
+func (p *EthereumApi) GetPeerCount(reply *interface{}) error {
+ *reply = p.xeth().PeerCount()
+ return nil
+}
+
+func (p *EthereumApi) GetIsListening(reply *interface{}) error {
+ *reply = p.xeth().IsListening()
+ return nil
+}
+
+func (p *EthereumApi) GetCoinbase(reply *interface{}) error {
+ *reply = p.xeth().Coinbase()
+ return nil
+}
+
+func (p *EthereumApi) Accounts(reply *interface{}) error {
+ *reply = p.xeth().Accounts()
+ return nil
+}
+
+func (p *EthereumApi) GetIsMining(reply *interface{}) error {
+ *reply = p.xeth().IsMining()
+ return nil
+}
+
+func (p *EthereumApi) SetMining(shouldmine bool, reply *interface{}) error {
+ *reply = p.xeth().SetMining(shouldmine)
+ return nil
+}
+
+func (p *EthereumApi) GetDefaultBlockAge(reply *interface{}) error {
+ *reply = p.defaultBlockAge
+ return nil
+}
+
+func (p *EthereumApi) SetDefaultBlockAge(defaultBlockAge int64, reply *interface{}) error {
+ p.defaultBlockAge = defaultBlockAge
+ p.setStateByBlockNumber(p.defaultBlockAge)
+
+ *reply = true
+ return nil
+}
+
+func (p *EthereumApi) BlockNumber(reply *interface{}) error {
+ *reply = p.xeth().Backend().ChainManager().CurrentBlock().Number()
+ return nil
+}
+
+func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+ *reply = p.xeth().TxCountAt(args.Address)
+ return nil
+}
+
+func (p *EthereumApi) GetBalanceAt(args *GetBalanceArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+ state := p.xeth().State().SafeGet(args.Address)
+ *reply = toHex(state.Balance().Bytes())
+ return nil
+}
+
+func (p *EthereumApi) GetCodeAt(args *GetCodeAtArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+ *reply = p.xeth().CodeAt(args.Address)
+ return nil
+}
+
+func (p *EthereumApi) GetCompilers(reply *interface{}) error {
+ c := []string{"serpent"}
+ *reply = c
+ return nil
+}
+
+func (p *EthereumApi) CompileSerpent(script string, reply *interface{}) error {
+ res, err := ethutil.Compile(script, false)
+ if err != nil {
+ return err
+ }
+ *reply = res
+ return nil
+}
+
+func (p *EthereumApi) Sha3(args *Sha3Args, reply *interface{}) error {
+ *reply = toHex(crypto.Sha3(fromHex(args.Data)))
+ return nil
+}
+
+func (p *EthereumApi) DbPut(args *DbArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+
+ p.db.Put([]byte(args.Database+args.Key), []byte(args.Value))
+ *reply = true
+ return nil
+}
+
+func (p *EthereumApi) DbGet(args *DbArgs, reply *interface{}) error {
+ err := args.requirements()
+ if err != nil {
+ return err
+ }
+
+ res, _ := p.db.Get([]byte(args.Database + args.Key))
+ *reply = string(res)
+ return nil
+}
+
+func (p *EthereumApi) NewWhisperIdentity(reply *interface{}) error {
+ *reply = p.xeth().Whisper().NewIdentity()
+ return nil
+}
+
+func (p *EthereumApi) NewWhisperFilter(args *xeth.Options, reply *interface{}) error {
+ var id int
+ args.Fn = func(msg xeth.WhisperMessage) {
+ p.messagesMut.Lock()
+ defer p.messagesMut.Unlock()
+ p.messages[id].add(msg) // = append(p.messages[id], msg)
+ }
+ id = p.xeth().Whisper().Watch(args)
+ p.messages[id] = &whisperFilter{timeout: time.Now()}
+ *reply = id
+ return nil
+}
+
+func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error {
+ self.messagesMut.Lock()
+ defer self.messagesMut.Unlock()
+
+ if self.messages[id] != nil {
+ *reply = self.messages[id].get()
+ }
+
+ return nil
+}
+
+func (p *EthereumApi) WhisperPost(args *WhisperMessageArgs, reply *interface{}) error {
+ err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topic, args.Priority, args.Ttl)
+ if err != nil {
+ return err
+ }
+
+ *reply = true
+ return nil
+}
+
+func (p *EthereumApi) HasWhisperIdentity(args string, reply *interface{}) error {
+ *reply = p.xeth().Whisper().HasIdentity(args)
+ return nil
+}
+
+func (p *EthereumApi) WhisperMessages(id int, reply *interface{}) error {
+ *reply = p.xeth().Whisper().Messages(id)
+ return nil
+}
+
+func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error {
+ // Spec at https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC
+ rpclogger.DebugDetailf("%T %s", req.Params, req.Params)
+ switch req.Method {
+ case "eth_coinbase":
+ return p.GetCoinbase(reply)
+ case "eth_listening":
+ return p.GetIsListening(reply)
+ case "eth_mining":
+ return p.GetIsMining(reply)
+ case "eth_setMining":
+ args, err := req.ToBoolArgs()
+ if err != nil {
+ return err
+ }
+ return p.SetMining(args, reply)
+ case "eth_defaultBlock":
+ return p.GetDefaultBlockAge(reply)
+ case "eth_setDefaultBlock":
+ args, err := req.ToIntArgs()
+ if err != nil {
+ return err
+ }
+ return p.SetDefaultBlockAge(int64(args), reply)
+ case "eth_peerCount":
+ return p.GetPeerCount(reply)
+ case "eth_number":
+ return p.BlockNumber(reply)
+ case "eth_accounts":
+ return p.Accounts(reply)
+ case "eth_countAt":
+ args, err := req.ToGetTxCountArgs()
+ if err != nil {
+ return err
+ }
+ return p.GetTxCountAt(args, reply)
+ case "eth_codeAt":
+ args, err := req.ToGetCodeAtArgs()
+ if err != nil {
+ return err
+ }
+ return p.GetCodeAt(args, reply)
+ case "eth_balanceAt":
+ args, err := req.ToGetBalanceArgs()
+ if err != nil {
+ return err
+ }
+ return p.GetBalanceAt(args, reply)
+ case "eth_stateAt":
+ args, err := req.ToGetStateArgs()
+ if err != nil {
+ return err
+ }
+ return p.GetStateAt(args, reply)
+ case "eth_storageAt":
+ args, err := req.ToStorageAtArgs()
+ if err != nil {
+ return err
+ }
+ return p.GetStorageAt(args, reply)
+ case "eth_blockByNumber", "eth_blockByHash":
+ args, err := req.ToGetBlockArgs()
+ if err != nil {
+ return err
+ }
+ return p.GetBlock(args, reply)
+ case "eth_transact":
+ args, err := req.ToNewTxArgs()
+ if err != nil {
+ return err
+ }
+ return p.Transact(args, reply)
+ case "eth_call":
+ args, err := req.ToNewTxArgs()
+ if err != nil {
+ return err
+ }
+ return p.Call(args, reply)
+ case "eth_newFilter":
+ args, err := req.ToFilterArgs()
+ if err != nil {
+ return err
+ }
+ return p.NewFilter(args, reply)
+ case "eth_newFilterString":
+ args, err := req.ToFilterStringArgs()
+ if err != nil {
+ return err
+ }
+ return p.NewFilterString(args, reply)
+ case "eth_uninstallFilter":
+ args, err := req.ToUninstallFilterArgs()
+ if err != nil {
+ return err
+ }
+ return p.UninstallFilter(args, reply)
+ case "eth_changed":
+ args, err := req.ToIdArgs()
+ if err != nil {
+ return err
+ }
+ return p.FilterChanged(args, reply)
+ case "eth_filterLogs":
+ args, err := req.ToIdArgs()
+ if err != nil {
+ return err
+ }
+ return p.Logs(args, reply)
+ case "eth_logs":
+ args, err := req.ToFilterArgs()
+ if err != nil {
+ return err
+ }
+ return p.AllLogs(args, reply)
+ case "eth_gasPrice":
+ *reply = toHex(defaultGasPrice.Bytes())
+ return nil
+ case "eth_register":
+ args, err := req.ToRegisterArgs()
+ if err != nil {
+ return err
+ }
+ return p.Register(args, reply)
+ case "eth_unregister":
+ args, err := req.ToRegisterArgs()
+ if err != nil {
+ return err
+ }
+ return p.Unregister(args, reply)
+ case "eth_watchTx":
+ args, err := req.ToWatchTxArgs()
+ if err != nil {
+ return err
+ }
+ return p.WatchTx(args, reply)
+ case "eth_compilers":
+ return p.GetCompilers(reply)
+ case "eth_serpent":
+ args, err := req.ToCompileArgs()
+ if err != nil {
+ return err
+ }
+ return p.CompileSerpent(args, reply)
+ case "web3_sha3":
+ args, err := req.ToSha3Args()
+ if err != nil {
+ return err
+ }
+ return p.Sha3(args, reply)
+ case "db_put":
+ args, err := req.ToDbPutArgs()
+ if err != nil {
+ return err
+ }
+ return p.DbPut(args, reply)
+ case "db_get":
+ args, err := req.ToDbGetArgs()
+ if err != nil {
+ return err
+ }
+ return p.DbGet(args, reply)
+ case "shh_newIdentity":
+ return p.NewWhisperIdentity(reply)
+ case "shh_newFilter":
+ args, err := req.ToWhisperFilterArgs()
+ if err != nil {
+ return err
+ }
+ return p.NewWhisperFilter(args, reply)
+ case "shh_changed":
+ args, err := req.ToIdArgs()
+ if err != nil {
+ return err
+ }
+ return p.MessagesChanged(args, reply)
+ case "shh_post":
+ args, err := req.ToWhisperPostArgs()
+ if err != nil {
+ return err
+ }
+ return p.WhisperPost(args, reply)
+ case "shh_haveIdentity":
+ args, err := req.ToWhisperHasIdentityArgs()
+ if err != nil {
+ return err
+ }
+ return p.HasWhisperIdentity(args, reply)
+ case "shh_getMessages":
+ args, err := req.ToIdArgs()
+ if err != nil {
+ return err
+ }
+ return p.WhisperMessages(args, reply)
+ default:
+ return NewErrorWithMessage(errNotImplemented, req.Method)
+ }
+
+ rpclogger.DebugDetailf("Reply: %T %s", reply, reply)
+ return nil
+}
+
+func (self *EthereumApi) xeth() *xeth.XEth {
+ self.xethMu.RLock()
+ defer self.xethMu.RUnlock()
+
+ return self.eth
+}
+
+func (self *EthereumApi) useState(statedb *state.StateDB) {
+ self.xethMu.Lock()
+ defer self.xethMu.Unlock()
+
+ self.eth = self.eth.UseState(statedb)
+}
+
+func t(f ui.Frontend) {
+ // Call the password dialog
+ ret, err := f.Call("PasswordDialog")
+ if err != nil {
+ fmt.Println(err)
+ }
+ // Get the first argument
+ t, _ := ret.Get(0)
+ fmt.Println("return:", t)
+}
diff --git a/rpc/api_test.go b/rpc/api_test.go
new file mode 100644
index 000000000..a9fc16cd3
--- /dev/null
+++ b/rpc/api_test.go
@@ -0,0 +1,38 @@
+package rpc
+
+import (
+ "sync"
+ "testing"
+ "time"
+)
+
+func TestFilterClose(t *testing.T) {
+ t.Skip()
+ api := &EthereumApi{
+ logs: make(map[int]*logFilter),
+ messages: make(map[int]*whisperFilter),
+ quit: make(chan struct{}),
+ }
+
+ filterTickerTime = 1
+ api.logs[0] = &logFilter{}
+ api.messages[0] = &whisperFilter{}
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go api.start()
+ go func() {
+ select {
+ case <-time.After(500 * time.Millisecond):
+ api.stop()
+ wg.Done()
+ }
+ }()
+ wg.Wait()
+ if len(api.logs) != 0 {
+ t.Error("expected logs to be empty")
+ }
+
+ if len(api.messages) != 0 {
+ t.Error("expected messages to be empty")
+ }
+}
diff --git a/rpc/args.go b/rpc/args.go
new file mode 100644
index 000000000..e839da8bf
--- /dev/null
+++ b/rpc/args.go
@@ -0,0 +1,258 @@
+package rpc
+
+import "encoding/json"
+
+import "github.com/ethereum/go-ethereum/core"
+
+type GetBlockArgs struct {
+ BlockNumber int32
+ Hash string
+}
+
+func (obj *GetBlockArgs) UnmarshalJSON(b []byte) (err error) {
+ argint, argstr := int32(0), ""
+ if err = json.Unmarshal(b, &argint); err == nil {
+ obj.BlockNumber = argint
+ return
+ }
+ if err = json.Unmarshal(b, &argstr); err == nil {
+ obj.Hash = argstr
+ return
+ }
+ return errDecodeArgs
+}
+
+type NewTxArgs struct {
+ From string `json:"from"`
+ To string `json:"to"`
+ Value string `json:"value"`
+ Gas string `json:"gas"`
+ GasPrice string `json:"gasPrice"`
+ Data string `json:"data"`
+}
+
+func (obj *NewTxArgs) UnmarshalJSON(b []byte) (err error) {
+ // Data can be either specified as "data" or "code" :-/
+ var ext struct {
+ From string
+ To string
+ Value string
+ Gas string
+ GasPrice string
+ Data string
+ Code string
+ }
+
+ if err = json.Unmarshal(b, &ext); err == nil {
+ if len(ext.Data) == 0 {
+ ext.Data = ext.Code
+ }
+ obj.From = ext.From
+ obj.To = ext.To
+ obj.Value = ext.Value
+ obj.Gas = ext.Gas
+ obj.GasPrice = ext.GasPrice
+ obj.Data = ext.Data
+
+ return
+ }
+
+ return errDecodeArgs
+}
+
+type PushTxArgs struct {
+ Tx string `json:"tx"`
+}
+
+func (obj *PushTxArgs) UnmarshalJSON(b []byte) (err error) {
+ arg0 := ""
+ if err = json.Unmarshal(b, &arg0); err == nil {
+ obj.Tx = arg0
+ return
+ }
+ return errDecodeArgs
+}
+
+func (a *PushTxArgs) requirementsPushTx() error {
+ if a.Tx == "" {
+ return NewErrorWithMessage(errArguments, "PushTx requires a 'tx' as argument")
+ }
+ return nil
+}
+
+type GetStorageArgs struct {
+ Address string
+}
+
+func (obj *GetStorageArgs) UnmarshalJSON(b []byte) (err error) {
+ if err = json.Unmarshal(b, &obj.Address); err != nil {
+ return errDecodeArgs
+ }
+ return
+}
+
+func (a *GetStorageArgs) requirements() error {
+ if len(a.Address) == 0 {
+ return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'address' value as argument")
+ }
+ return nil
+}
+
+type GetStateArgs struct {
+ Address string
+ Key string
+}
+
+func (obj *GetStateArgs) UnmarshalJSON(b []byte) (err error) {
+ arg0 := ""
+ if err = json.Unmarshal(b, &arg0); err == nil {
+ obj.Address = arg0
+ return
+ }
+ return errDecodeArgs
+}
+
+func (a *GetStateArgs) requirements() error {
+ if a.Address == "" {
+ return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'address' value as argument")
+ }
+ if a.Key == "" {
+ return NewErrorWithMessage(errArguments, "GetStorageAt requires an 'key' value as argument")
+ }
+ return nil
+}
+
+type GetTxCountArgs struct {
+ Address string `json:"address"`
+}
+
+func (obj *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) {
+ arg0 := ""
+ if err = json.Unmarshal(b, &arg0); err == nil {
+ obj.Address = arg0
+ return
+ }
+ return errDecodeArgs
+}
+
+func (a *GetTxCountArgs) requirements() error {
+ if a.Address == "" {
+ return NewErrorWithMessage(errArguments, "GetTxCountAt requires an 'address' value as argument")
+ }
+ return nil
+}
+
+type GetBalanceArgs struct {
+ Address string
+}
+
+func (obj *GetBalanceArgs) UnmarshalJSON(b []byte) (err error) {
+ arg0 := ""
+ if err = json.Unmarshal(b, &arg0); err == nil {
+ obj.Address = arg0
+ return
+ }
+ return errDecodeArgs
+}
+
+func (a *GetBalanceArgs) requirements() error {
+ if a.Address == "" {
+ return NewErrorWithMessage(errArguments, "GetBalanceAt requires an 'address' value as argument")
+ }
+ return nil
+}
+
+type GetCodeAtArgs struct {
+ Address string
+}
+
+func (obj *GetCodeAtArgs) UnmarshalJSON(b []byte) (err error) {
+ arg0 := ""
+ if err = json.Unmarshal(b, &arg0); err == nil {
+ obj.Address = arg0
+ return
+ }
+ return errDecodeArgs
+}
+
+func (a *GetCodeAtArgs) requirements() error {
+ if a.Address == "" {
+ return NewErrorWithMessage(errArguments, "GetCodeAt requires an 'address' value as argument")
+ }
+ return nil
+}
+
+type Sha3Args struct {
+ Data string
+}
+
+func (obj *Sha3Args) UnmarshalJSON(b []byte) (err error) {
+ if err = json.Unmarshal(b, &obj.Data); err != nil {
+ return errDecodeArgs
+ }
+ return
+}
+
+type FilterOptions struct {
+ Earliest int64
+ Latest int64
+ Address interface{}
+ Topic []string
+ Skip int
+ Max int
+}
+
+func toFilterOptions(options *FilterOptions) core.FilterOptions {
+ var opts core.FilterOptions
+
+ // Convert optional address slice/string to byte slice
+ if str, ok := options.Address.(string); ok {
+ opts.Address = [][]byte{fromHex(str)}
+ } else if slice, ok := options.Address.([]interface{}); ok {
+ bslice := make([][]byte, len(slice))
+ for i, addr := range slice {
+ if saddr, ok := addr.(string); ok {
+ bslice[i] = fromHex(saddr)
+ }
+ }
+ opts.Address = bslice
+ }
+
+ opts.Earliest = options.Earliest
+ opts.Latest = options.Latest
+ opts.Topics = make([][]byte, len(options.Topic))
+ for i, topic := range options.Topic {
+ opts.Topics[i] = fromHex(topic)
+ }
+
+ return opts
+}
+
+type FilterChangedArgs struct {
+ n int
+}
+
+type DbArgs struct {
+ Database string
+ Key string
+ Value string
+}
+
+func (a *DbArgs) requirements() error {
+ if len(a.Database) == 0 {
+ return NewErrorWithMessage(errArguments, "DbPutArgs requires an 'Database' value as argument")
+ }
+ if len(a.Key) == 0 {
+ return NewErrorWithMessage(errArguments, "DbPutArgs requires an 'Key' value as argument")
+ }
+ return nil
+}
+
+type WhisperMessageArgs struct {
+ Payload string
+ To string
+ From string
+ Topic []string
+ Priority uint32
+ Ttl uint32
+}
diff --git a/rpc/http/server.go b/rpc/http/server.go
new file mode 100644
index 000000000..fa66eed48
--- /dev/null
+++ b/rpc/http/server.go
@@ -0,0 +1,114 @@
+/*
+ This file is part of go-ethereum
+
+ go-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ go-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+package rpchttp
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/xeth"
+)
+
+var rpchttplogger = logger.NewLogger("RPC-HTTP")
+var JSON rpc.JsonWrapper
+
+func NewRpcHttpServer(pipe *xeth.XEth, port int) (*RpcHttpServer, error) {
+ sport := fmt.Sprintf("127.0.0.1:%d", port)
+ l, err := net.Listen("tcp", sport)
+ if err != nil {
+ return nil, err
+ }
+
+ return &RpcHttpServer{
+ listener: l,
+ quit: make(chan bool),
+ pipe: pipe,
+ port: port,
+ }, nil
+}
+
+type RpcHttpServer struct {
+ quit chan bool
+ listener net.Listener
+ pipe *xeth.XEth
+ port int
+}
+
+func (s *RpcHttpServer) exitHandler() {
+out:
+ for {
+ select {
+ case <-s.quit:
+ s.listener.Close()
+ break out
+ }
+ }
+
+ rpchttplogger.Infoln("Shutdown RPC-HTTP server")
+}
+
+func (s *RpcHttpServer) Stop() {
+ close(s.quit)
+}
+
+func (s *RpcHttpServer) Start() {
+ rpchttplogger.Infof("Starting RPC-HTTP server on port %d", s.port)
+ go s.exitHandler()
+
+ api := rpc.NewEthereumApi(s.pipe)
+ h := s.apiHandler(api)
+ http.Handle("/", h)
+
+ err := http.Serve(s.listener, nil)
+ // FIX Complains on shutdown due to listner already being closed
+ if err != nil {
+ rpchttplogger.Errorln("Error on RPC-HTTP interface:", err)
+ }
+}
+
+func (s *RpcHttpServer) apiHandler(api *rpc.EthereumApi) http.Handler {
+ var jsonrpcver string = "2.0"
+ fn := func(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ rpchttplogger.DebugDetailln("Handling request")
+
+ reqParsed, reqerr := JSON.ParseRequestBody(req)
+ if reqerr != nil {
+ jsonerr := &rpc.RpcErrorObject{-32700, "Error: Could not parse request"}
+ JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr})
+ return
+ }
+
+ var response interface{}
+ reserr := api.GetRequestReply(&reqParsed, &response)
+ if reserr != nil {
+ rpchttplogger.Warnln(reserr)
+ jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()}
+ JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr})
+ return
+ }
+
+ rpchttplogger.DebugDetailf("Generated response: %T %s", response, response)
+ JSON.Send(w, &rpc.RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response})
+ }
+
+ return http.HandlerFunc(fn)
+}
diff --git a/rpc/messages.go b/rpc/messages.go
new file mode 100644
index 000000000..b37d8229d
--- /dev/null
+++ b/rpc/messages.go
@@ -0,0 +1,419 @@
+/*
+ This file is part of go-ethereum
+
+ go-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ go-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+package rpc
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/xeth"
+)
+
+var (
+ errArguments = errors.New("Error: Insufficient arguments")
+ errNotImplemented = errors.New("Error: Method not implemented")
+ errUnknown = errors.New("Error: Unknown error")
+ errDecodeArgs = errors.New("Error: Could not decode arguments")
+)
+
+type RpcRequest struct {
+ ID interface{} `json:"id"`
+ JsonRpc string `json:"jsonrpc"`
+ Method string `json:"method"`
+ Params []json.RawMessage `json:"params"`
+}
+
+type RpcSuccessResponse struct {
+ ID interface{} `json:"id"`
+ JsonRpc string `json:"jsonrpc"`
+ Result interface{} `json:"result"`
+}
+
+type RpcErrorResponse struct {
+ ID interface{} `json:"id"`
+ JsonRpc string `json:"jsonrpc"`
+ Error *RpcErrorObject `json:"error"`
+}
+
+type RpcErrorObject struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ // Data interface{} `json:"data"`
+}
+
+func NewErrorWithMessage(err error, msg string) error {
+ return fmt.Errorf("%s: %s", err.Error(), msg)
+}
+
+func (req *RpcRequest) ToSha3Args() (*Sha3Args, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(Sha3Args)
+ r := bytes.NewReader(req.Params[0])
+ if err := json.NewDecoder(r).Decode(args); err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToGetBlockArgs() (*GetBlockArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(GetBlockArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToNewTxArgs() (*NewTxArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(NewTxArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToPushTxArgs() (*PushTxArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(PushTxArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToGetStateArgs() (*GetStateArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(GetStateArgs)
+ // TODO need to pass both arguments
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToStorageAtArgs() (*GetStorageArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(GetStorageArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToGetTxCountArgs() (*GetTxCountArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(GetTxCountArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToGetBalanceArgs() (*GetBalanceArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(GetBalanceArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToGetCodeAtArgs() (*GetCodeAtArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(GetCodeAtArgs)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToBoolArgs() (bool, error) {
+ if len(req.Params) < 1 {
+ return false, errArguments
+ }
+
+ var args bool
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return false, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToIntArgs() (int, error) {
+ if len(req.Params) < 1 {
+ return 0, errArguments
+ }
+
+ var args int
+ if err := json.Unmarshal(req.Params[0], &args); err != nil {
+ return 0, errArguments
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToCompileArgs() (string, error) {
+ if len(req.Params) < 1 {
+ return "", errArguments
+ }
+
+ var args string
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return "", errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToFilterArgs() (*FilterOptions, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ args := new(FilterOptions)
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(args)
+ if err != nil {
+ return nil, errDecodeArgs
+ }
+ return args, nil
+}
+
+func (req *RpcRequest) ToFilterStringArgs() (string, error) {
+ if len(req.Params) < 1 {
+ return "", errArguments
+ }
+
+ var args string
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return "", errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToUninstallFilterArgs() (int, error) {
+ if len(req.Params) < 1 {
+ return 0, errArguments
+ }
+
+ var args int
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return 0, errDecodeArgs
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToFilterChangedArgs() (int, error) {
+ if len(req.Params) < 1 {
+ return 0, errArguments
+ }
+
+ var id int
+ r := bytes.NewReader(req.Params[0])
+ err := json.NewDecoder(r).Decode(&id)
+ if err != nil {
+ return 0, errDecodeArgs
+ }
+ return id, nil
+}
+
+func (req *RpcRequest) ToDbPutArgs() (*DbArgs, error) {
+ if len(req.Params) < 3 {
+ return nil, errArguments
+ }
+
+ var args DbArgs
+ err := json.Unmarshal(req.Params[0], &args.Database)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+ err = json.Unmarshal(req.Params[1], &args.Key)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+ err = json.Unmarshal(req.Params[2], &args.Value)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+
+ return &args, nil
+}
+
+func (req *RpcRequest) ToDbGetArgs() (*DbArgs, error) {
+ if len(req.Params) < 2 {
+ return nil, errArguments
+ }
+
+ var args DbArgs
+ err := json.Unmarshal(req.Params[0], &args.Database)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+
+ err = json.Unmarshal(req.Params[1], &args.Key)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+
+ return &args, nil
+}
+
+func (req *RpcRequest) ToWhisperFilterArgs() (*xeth.Options, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ var args xeth.Options
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return nil, NewErrorWithMessage(errDecodeArgs, err.Error())
+ }
+
+ return &args, nil
+}
+
+func (req *RpcRequest) ToIdArgs() (int, error) {
+ if len(req.Params) < 1 {
+ return 0, errArguments
+ }
+
+ var id int
+ err := json.Unmarshal(req.Params[0], &id)
+ if err != nil {
+ return 0, errDecodeArgs
+ }
+
+ return id, nil
+}
+
+func (req *RpcRequest) ToWhisperPostArgs() (*WhisperMessageArgs, error) {
+ if len(req.Params) < 1 {
+ return nil, errArguments
+ }
+
+ var args WhisperMessageArgs
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return nil, err
+ }
+
+ return &args, nil
+}
+
+func (req *RpcRequest) ToWhisperHasIdentityArgs() (string, error) {
+ if len(req.Params) < 1 {
+ return "", errArguments
+ }
+
+ var args string
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return "", err
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToRegisterArgs() (string, error) {
+ if len(req.Params) < 1 {
+ return "", errArguments
+ }
+
+ var args string
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return "", err
+ }
+
+ return args, nil
+}
+
+func (req *RpcRequest) ToWatchTxArgs() (string, error) {
+ if len(req.Params) < 1 {
+ return "", errArguments
+ }
+
+ var args string
+ err := json.Unmarshal(req.Params[0], &args)
+ if err != nil {
+ return "", err
+ }
+
+ return args, nil
+}
diff --git a/rpc/util.go b/rpc/util.go
new file mode 100644
index 000000000..3e8ca3fef
--- /dev/null
+++ b/rpc/util.go
@@ -0,0 +1,139 @@
+/*
+ This file is part of go-ethereum
+
+ go-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ go-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+package rpc
+
+import (
+ "encoding/json"
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/ethereum/go-ethereum/ethutil"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/state"
+ "github.com/ethereum/go-ethereum/xeth"
+)
+
+var rpclogger = logger.NewLogger("RPC")
+
+type JsonWrapper struct{}
+
+func (self JsonWrapper) Send(writer io.Writer, v interface{}) (n int, err error) {
+ var payload []byte
+ payload, err = json.Marshal(v)
+ if err != nil {
+ rpclogger.Fatalln("Error marshalling JSON", err)
+ return 0, err
+ }
+ rpclogger.DebugDetailf("Sending payload: %s", payload)
+
+ return writer.Write(payload)
+}
+
+func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) {
+ var reqParsed RpcRequest
+
+ // Convert JSON to native types
+ d := json.NewDecoder(req.Body)
+ defer req.Body.Close()
+ err := d.Decode(&reqParsed)
+
+ if err != nil {
+ rpclogger.Errorln("Error decoding JSON: ", err)
+ return reqParsed, err
+ }
+
+ rpclogger.DebugDetailf("Parsed request: %s", reqParsed)
+
+ return reqParsed, nil
+}
+
+func toHex(b []byte) string {
+ return "0x" + ethutil.Bytes2Hex(b)
+}
+func fromHex(s string) []byte {
+ if len(s) > 1 {
+ if s[0:2] == "0x" {
+ s = s[2:]
+ }
+ return ethutil.Hex2Bytes(s)
+ }
+ return nil
+}
+
+type RpcServer interface {
+ Start()
+ Stop()
+}
+
+type Log struct {
+ Address string `json:"address"`
+ Topic []string `json:"topic"`
+ Data string `json:"data"`
+ Number uint64 `json:"number"`
+}
+
+func toLogs(logs state.Logs) (ls []Log) {
+ ls = make([]Log, len(logs))
+
+ for i, log := range logs {
+ var l Log
+ l.Topic = make([]string, len(log.Topics()))
+ l.Address = toHex(log.Address())
+ l.Data = toHex(log.Data())
+ l.Number = log.Number()
+ for j, topic := range log.Topics() {
+ l.Topic[j] = toHex(topic)
+ }
+ ls[i] = l
+ }
+
+ return
+}
+
+type whisperFilter struct {
+ messages []xeth.WhisperMessage
+ timeout time.Time
+ id int
+}
+
+func (w *whisperFilter) add(msgs ...xeth.WhisperMessage) {
+ w.messages = append(w.messages, msgs...)
+}
+func (w *whisperFilter) get() []xeth.WhisperMessage {
+ w.timeout = time.Now()
+ tmp := w.messages
+ w.messages = nil
+ return tmp
+}
+
+type logFilter struct {
+ logs state.Logs
+ timeout time.Time
+ id int
+}
+
+func (l *logFilter) add(logs ...state.Log) {
+ l.logs = append(l.logs, logs...)
+}
+
+func (l *logFilter) get() state.Logs {
+ l.timeout = time.Now()
+ tmp := l.logs
+ l.logs = nil
+ return tmp
+}
diff --git a/rpc/ws/server.go b/rpc/ws/server.go
new file mode 100644
index 000000000..2c2988f5d
--- /dev/null
+++ b/rpc/ws/server.go
@@ -0,0 +1,121 @@
+/*
+ This file is part of go-ethereum
+
+ go-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ go-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+package rpcws
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/xeth"
+ "golang.org/x/net/websocket"
+)
+
+var wslogger = logger.NewLogger("RPC-WS")
+var JSON rpc.JsonWrapper
+
+type WebSocketServer struct {
+ pipe *xeth.XEth
+ port int
+ doneCh chan bool
+ listener net.Listener
+}
+
+func NewWebSocketServer(pipe *xeth.XEth, port int) (*WebSocketServer, error) {
+ sport := fmt.Sprintf(":%d", port)
+ l, err := net.Listen("tcp", sport)
+ if err != nil {
+ return nil, err
+ }
+
+ return &WebSocketServer{
+ pipe,
+ port,
+ make(chan bool),
+ l,
+ }, nil
+}
+
+func (self *WebSocketServer) handlerLoop() {
+ for {
+ select {
+ case <-self.doneCh:
+ wslogger.Infoln("Shutdown RPC-WS server")
+ return
+ }
+ }
+}
+
+func (self *WebSocketServer) Stop() {
+ close(self.doneCh)
+}
+
+func (self *WebSocketServer) Start() {
+ wslogger.Infof("Starting RPC-WS server on port %d", self.port)
+ go self.handlerLoop()
+
+ api := rpc.NewEthereumApi(self.pipe)
+ h := self.apiHandler(api)
+ http.Handle("/ws", h)
+
+ err := http.Serve(self.listener, nil)
+ if err != nil {
+ wslogger.Errorln("Error on RPC-WS interface:", err)
+ }
+}
+
+func (s *WebSocketServer) apiHandler(api *rpc.EthereumApi) http.Handler {
+ fn := func(w http.ResponseWriter, req *http.Request) {
+ h := sockHandler(api)
+ s := websocket.Server{Handler: h}
+ s.ServeHTTP(w, req)
+ }
+
+ return http.HandlerFunc(fn)
+}
+
+func sockHandler(api *rpc.EthereumApi) websocket.Handler {
+ var jsonrpcver string = "2.0"
+ fn := func(conn *websocket.Conn) {
+ for {
+ wslogger.Debugln("Handling connection")
+ var reqParsed rpc.RpcRequest
+
+ // reqParsed, reqerr := JSON.ParseRequestBody(conn.Request())
+ if err := websocket.JSON.Receive(conn, &reqParsed); err != nil {
+ jsonerr := &rpc.RpcErrorObject{-32700, "Error: Could not parse request"}
+ JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr})
+ continue
+ }
+
+ var response interface{}
+ reserr := api.GetRequestReply(&reqParsed, &response)
+ if reserr != nil {
+ wslogger.Warnln(reserr)
+ jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()}
+ JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr})
+ continue
+ }
+
+ wslogger.Debugf("Generated response: %T %s", response, response)
+ JSON.Send(conn, &rpc.RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response})
+ }
+ }
+ return websocket.Handler(fn)
+}