diff options
Diffstat (limited to 'rpc')
-rw-r--r-- | rpc/args.go | 55 | ||||
-rw-r--r-- | rpc/http/server.go | 4 | ||||
-rw-r--r-- | rpc/message.go | 81 | ||||
-rw-r--r-- | rpc/packages.go | 242 | ||||
-rw-r--r-- | rpc/packages_test.go | 37 | ||||
-rw-r--r-- | rpc/util.go | 43 | ||||
-rw-r--r-- | rpc/ws/server.go | 7 |
7 files changed, 410 insertions, 59 deletions
diff --git a/rpc/args.go b/rpc/args.go index 84b076d4a..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,13 +38,42 @@ 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"` } func (obj *PushTxArgs) UnmarshalJSON(b []byte) (err error) { arg0 := "" - if err = json.Unmarshal(b, arg0); err == nil { + if err = json.Unmarshal(b, &arg0); err == nil { obj.Tx = arg0 return } @@ -82,7 +112,7 @@ type GetStateArgs struct { func (obj *GetStateArgs) UnmarshalJSON(b []byte) (err error) { arg0 := "" - if err = json.Unmarshal(b, arg0); err == nil { + if err = json.Unmarshal(b, &arg0); err == nil { obj.Address = arg0 return } @@ -114,7 +144,7 @@ type GetTxCountArgs struct { func (obj *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { arg0 := "" - if err = json.Unmarshal(b, arg0); err == nil { + if err = json.Unmarshal(b, &arg0); err == nil { obj.Address = arg0 return } @@ -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/http/server.go b/rpc/http/server.go index a34400a77..dd6ba68e3 100644 --- a/rpc/http/server.go +++ b/rpc/http/server.go @@ -30,7 +30,7 @@ var rpchttplogger = logger.NewLogger("RPC-HTTP") var JSON rpc.JsonWrapper func NewRpcHttpServer(pipe *xeth.XEth, port int) (*RpcHttpServer, error) { - sport := fmt.Sprintf(":%d", port) + sport := fmt.Sprintf("127.0.0.1:%d", port) l, err := net.Listen("tcp", sport) if err != nil { return nil, err @@ -102,7 +102,7 @@ func (s *RpcHttpServer) apiHandler(api *rpc.EthereumApi) http.Handler { if reserr != nil { rpchttplogger.Warnln(reserr) jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()} - JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: &reqParsed.ID, Error: jsonerr}) + JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) return } diff --git a/rpc/message.go b/rpc/message.go index 78dc6e2ff..825ede05b 100644 --- a/rpc/message.go +++ b/rpc/message.go @@ -34,20 +34,20 @@ const ( ) type RpcRequest struct { + ID interface{} `json:"id"` JsonRpc string `json:"jsonrpc"` - ID int `json:"id"` Method string `json:"method"` Params []json.RawMessage `json:"params"` } type RpcSuccessResponse struct { - ID int `json:"id"` + ID interface{} `json:"id"` JsonRpc string `json:"jsonrpc"` Result interface{} `json:"result"` } type RpcErrorResponse struct { - ID *int `json:"id"` + ID interface{} `json:"id"` JsonRpc string `json:"jsonrpc"` Error *RpcErrorObject `json:"error"` } @@ -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 ac3127356..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 @@ -109,18 +145,32 @@ func (self *EthereumApi) NewFilterString(args string, reply *interface{}) error } func (self *EthereumApi) FilterChanged(id int, reply *interface{}) error { - self.logMut.RLock() - defer self.logMut.RUnlock() - - *reply = toLogs(self.logs[id]) + self.logMut.Lock() + defer self.logMut.Unlock() - 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 @@ -309,18 +388,18 @@ func (p *EthereumApi) NewWhisperFilter(args *xeth.Options, reply *interface{}) e } func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error { - self.messagesMut.RLock() - defer self.messagesMut.RUnlock() - - *reply = self.messages[id] + self.messagesMut.Lock() + defer self.messagesMut.Unlock() - 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 509d9a17d..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") @@ -47,7 +49,6 @@ func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) // Convert JSON to native types d := json.NewDecoder(req.Body) - // d.UseNumber() defer req.Body.Close() err := d.Decode(&reqParsed) @@ -55,6 +56,7 @@ func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) rpclogger.Errorln("Error decoding JSON: ", err) return reqParsed, err } + rpclogger.DebugDetailf("Parsed request: %s", reqParsed) return reqParsed, nil @@ -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 6be4e8ce9..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") @@ -94,9 +94,10 @@ func sockHandler(api *rpc.EthereumApi) websocket.Handler { var jsonrpcver string = "2.0" fn := func(conn *websocket.Conn) { for { - wslogger.Debugln("Handling request") + 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, rpc.ErrorParseRequest} JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) @@ -108,7 +109,7 @@ func sockHandler(api *rpc.EthereumApi) websocket.Handler { if reserr != nil { wslogger.Warnln(reserr) jsonerr := &rpc.RpcErrorObject{-32603, reserr.Error()} - JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: &reqParsed.ID, Error: jsonerr}) + JSON.Send(conn, &rpc.RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) continue } |