diff options
Diffstat (limited to 'rpc')
-rw-r--r-- | rpc/args.go | 49 | ||||
-rw-r--r-- | rpc/message.go | 75 | ||||
-rw-r--r-- | rpc/packages.go | 234 | ||||
-rw-r--r-- | rpc/packages_test.go | 37 | ||||
-rw-r--r-- | rpc/util.go | 41 | ||||
-rw-r--r-- | rpc/ws/server.go | 2 |
6 files changed, 394 insertions, 44 deletions
diff --git a/rpc/args.go b/rpc/args.go index 12e3103bc..f730819fd 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -1,6 +1,7 @@ package rpc import "encoding/json" + import "github.com/ethereum/go-ethereum/core" type GetBlockArgs struct { @@ -37,6 +38,35 @@ type NewTxArgs struct { 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 NewErrorResponse(ErrorDecodeArgs) +} + type PushTxArgs struct { Tx string `json:"tx"` } @@ -203,7 +233,7 @@ func (obj *Sha3Args) UnmarshalJSON(b []byte) (err error) { type FilterOptions struct { Earliest int64 Latest int64 - Address string + Address interface{} Topic []string Skip int Max int @@ -211,9 +241,22 @@ type FilterOptions struct { 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.Address = fromHex(options.Address) opts.Topics = make([][]byte, len(options.Topic)) for i, topic := range options.Topic { opts.Topics[i] = fromHex(topic) @@ -246,7 +289,7 @@ type WhisperMessageArgs struct { Payload string To string From string - Topics []string + Topic []string Priority uint32 Ttl uint32 } diff --git a/rpc/message.go b/rpc/message.go index 7983e003d..825ede05b 100644 --- a/rpc/message.go +++ b/rpc/message.go @@ -201,6 +201,36 @@ func (req *RpcRequest) ToGetCodeAtArgs() (*GetCodeAtArgs, error) { return args, nil } +func (req *RpcRequest) ToBoolArgs() (bool, error) { + if len(req.Params) < 1 { + return false, NewErrorResponse(ErrorArguments) + } + + var args bool + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return false, NewErrorResponse(ErrorDecodeArgs) + } + + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToCompileArgs() (string, error) { + if len(req.Params) < 1 { + return "", NewErrorResponse(ErrorArguments) + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", NewErrorResponse(ErrorDecodeArgs) + } + + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + func (req *RpcRequest) ToFilterArgs() (*FilterOptions, error) { if len(req.Params) < 1 { return nil, NewErrorResponse(ErrorArguments) @@ -231,6 +261,21 @@ func (req *RpcRequest) ToFilterStringArgs() (string, error) { return args, nil } +func (req *RpcRequest) ToUninstallFilterArgs() (int, error) { + if len(req.Params) < 1 { + return 0, NewErrorResponse(ErrorArguments) + } + + var args int + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return 0, NewErrorResponse(ErrorDecodeArgs) + } + + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + func (req *RpcRequest) ToFilterChangedArgs() (int, error) { if len(req.Params) < 1 { return 0, NewErrorResponse(ErrorArguments) @@ -301,7 +346,7 @@ func (req *RpcRequest) ToWhisperFilterArgs() (*xeth.Options, error) { return &args, nil } -func (req *RpcRequest) ToWhisperIdArgs() (int, error) { +func (req *RpcRequest) ToIdArgs() (int, error) { if len(req.Params) < 1 { return 0, NewErrorResponse(ErrorArguments) } @@ -342,3 +387,31 @@ func (req *RpcRequest) ToWhisperHasIdentityArgs() (string, error) { rpclogger.DebugDetailf("%T %v", args, args) return args, nil } + +func (req *RpcRequest) ToRegisterArgs() (string, error) { + if len(req.Params) < 1 { + return "", NewErrorResponse(ErrorArguments) + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", err + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToWatchTxArgs() (string, error) { + if len(req.Params) < 1 { + return "", NewErrorResponse(ErrorArguments) + } + + var args string + err := json.Unmarshal(req.Params[0], &args) + if err != nil { + return "", err + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} diff --git a/rpc/packages.go b/rpc/packages.go index ef31ff1e1..b51bde7ce 100644 --- a/rpc/packages.go +++ b/rpc/packages.go @@ -1,21 +1,4 @@ /* - 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/>. -*/ -/* - 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() @@ -30,6 +13,7 @@ import ( "math/big" "strings" "sync" + "time" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -48,13 +32,17 @@ const ( type EthereumApi struct { xeth *xeth.XEth + quit chan struct{} filterManager *filter.FilterManager logMut sync.RWMutex - logs map[int]state.Logs + logs map[int]*logFilter messagesMut sync.RWMutex - messages map[int][]xeth.WhisperMessage + messages map[int]*whisperFilter + // Register keeps a list of accounts and transaction data + regmut sync.Mutex + register map[string][]*NewTxArgs db ethutil.Database } @@ -63,16 +51,48 @@ func NewEthereumApi(eth *xeth.XEth) *EthereumApi { db, _ := ethdb.NewLDBDatabase("dapps") api := &EthereumApi{ xeth: eth, + quit: make(chan struct{}), filterManager: filter.NewFilterManager(eth.Backend().EventMux()), - logs: make(map[int]state.Logs), - messages: make(map[int][]xeth.WhisperMessage), + logs: make(map[int]*logFilter), + messages: make(map[int]*whisperFilter), db: db, } go api.filterManager.Start() + go api.start() return api } +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()) @@ -81,20 +101,36 @@ func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) erro self.logMut.Lock() defer self.logMut.Unlock() - self.logs[id] = append(self.logs[id], logs...) + 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.logs[id] = append(self.logs[id], &state.StateLog{}) + self.logMut.Lock() + defer self.logMut.Unlock() + + if self.logs[id] == nil { + self.logs[id] = &logFilter{timeout: time.Now()} + } + + self.logs[id].add(&state.StateLog{}) } if args == "pending" { filter.PendingCallback = callback @@ -112,15 +148,29 @@ func (self *EthereumApi) FilterChanged(id int, reply *interface{}) error { self.logMut.Lock() defer self.logMut.Unlock() - *reply = toLogs(self.logs[id]) - - self.logs[id] = nil // empty the logs + 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 @@ -149,8 +199,14 @@ func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { args.GasPrice = defaultGasPrice } - result, _ := p.xeth.Transact( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) - *reply = result + // TODO if no_private_key then + if _, exists := p.register[args.From]; exists { + p.register[args.From] = append(p.register[args.From], args) + } else { + result, _ := p.xeth.Transact( /* TODO specify account */ args.To, args.Value, args.Gas, args.GasPrice, args.Data) + *reply = result + } + return nil } @@ -231,6 +287,11 @@ func (p *EthereumApi) GetIsMining(reply *interface{}) error { return nil } +func (p *EthereumApi) SetMining(shouldmine bool, reply *interface{}) error { + *reply = p.xeth.SetMining(shouldmine) + return nil +} + func (p *EthereumApi) BlockNumber(reply *interface{}) error { *reply = p.xeth.Backend().ChainManager().CurrentBlock().Number() return nil @@ -264,6 +325,21 @@ func (p *EthereumApi) GetCodeAt(args *GetCodeAtArgs, reply *interface{}) error { 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 @@ -301,7 +377,10 @@ func (p *EthereumApi) NewWhisperFilter(args *xeth.Options, reply *interface{}) e args.Fn = func(msg xeth.WhisperMessage) { p.messagesMut.Lock() defer p.messagesMut.Unlock() - p.messages[id] = append(p.messages[id], msg) + if p.messages[id] == nil { + p.messages[id] = &whisperFilter{timeout: time.Now()} + } + p.messages[id].add(msg) // = append(p.messages[id], msg) } id = p.xeth.Whisper().Watch(args) *reply = id @@ -312,15 +391,15 @@ func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error { self.messagesMut.Lock() defer self.messagesMut.Unlock() - *reply = self.messages[id] - - self.messages[id] = nil // empty the messages + 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.Topics, args.Priority, args.Ttl) + err := p.xeth.Whisper().Post(args.Payload, args.To, args.From, args.Topic, args.Priority, args.Ttl) if err != nil { return err } @@ -349,6 +428,12 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error 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_peerCount": return p.GetPeerCount(reply) case "eth_number": @@ -415,15 +500,59 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error 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.ToFilterChangedArgs() + 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 = defaultGasPrice 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 { @@ -451,7 +580,7 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error } return p.NewWhisperFilter(args, reply) case "shh_changed": - args, err := req.ToWhisperIdArgs() + args, err := req.ToIdArgs() if err != nil { return err } @@ -469,7 +598,7 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error } return p.HasWhisperIdentity(args, reply) case "shh_getMessages": - args, err := req.ToWhisperIdArgs() + args, err := req.ToIdArgs() if err != nil { return err } @@ -481,3 +610,36 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error rpclogger.DebugDetailf("Reply: %T %s", reply, reply) return nil } + +var filterTickerTime = 15 * time.Second + +func (self *EthereumApi) start() { + timer := time.NewTicker(filterTickerTime) +done: + for { + select { + case <-timer.C: + self.logMut.Lock() + self.messagesMut.Lock() + for id, filter := range self.logs { + if time.Since(filter.timeout) > 20*time.Second { + delete(self.logs, id) + } + } + + for id, filter := range self.messages { + if time.Since(filter.timeout) > 20*time.Second { + delete(self.messages, id) + } + } + self.logMut.Unlock() + self.messagesMut.Unlock() + case <-self.quit: + break done + } + } +} + +func (self *EthereumApi) stop() { + close(self.quit) +} diff --git a/rpc/packages_test.go b/rpc/packages_test.go new file mode 100644 index 000000000..037fd78b3 --- /dev/null +++ b/rpc/packages_test.go @@ -0,0 +1,37 @@ +package rpc + +import ( + "sync" + "testing" + "time" +) + +func TestFilterClose(t *testing.T) { + 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/util.go b/rpc/util.go index 679d83754..1939b3474 100644 --- a/rpc/util.go +++ b/rpc/util.go @@ -20,10 +20,12 @@ 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") @@ -80,8 +82,9 @@ type RpcServer interface { type Log struct { Address string `json:"address"` - Topics []string `json:"topics"` + Topic []string `json:"topics"` Data string `json:"data"` + Number uint64 `json:"number"` } func toLogs(logs state.Logs) (ls []Log) { @@ -89,14 +92,46 @@ func toLogs(logs state.Logs) (ls []Log) { for i, log := range logs { var l Log - l.Topics = make([]string, len(log.Topics())) + 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.Topics[j] = toHex(topic) + l.Topic[j] = toHex(topic) } ls[i] = l } return } + +type whisperFilter struct { + messages []xeth.WhisperMessage + timeout time.Time +} + +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 +} + +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 index 100713c10..b8cc2fa6b 100644 --- a/rpc/ws/server.go +++ b/rpc/ws/server.go @@ -21,10 +21,10 @@ import ( "net" "net/http" - "code.google.com/p/go.net/websocket" "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") |