diff options
Diffstat (limited to 'rpc')
-rw-r--r-- | rpc/args.go | 196 | ||||
-rw-r--r-- | rpc/http/server.go | 111 | ||||
-rw-r--r-- | rpc/json.go | 44 | ||||
-rw-r--r-- | rpc/message.go | 188 | ||||
-rw-r--r-- | rpc/packages.go | 355 | ||||
-rw-r--r-- | rpc/server.go | 67 | ||||
-rw-r--r-- | rpc/writer.go | 75 | ||||
-rw-r--r-- | rpc/ws/server.go | 123 |
8 files changed, 789 insertions, 370 deletions
diff --git a/rpc/args.go b/rpc/args.go new file mode 100644 index 000000000..0c3087151 --- /dev/null +++ b/rpc/args.go @@ -0,0 +1,196 @@ +package rpc + +import "encoding/json" + +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 NewErrorResponse(ErrorDecodeArgs) +} + +func (obj *GetBlockArgs) requirements() error { + if obj.BlockNumber == 0 && obj.Hash == "" { + return NewErrorResponse("GetBlock requires either a block 'number' or a block 'hash' as argument") + } + return nil +} + +type NewTxArgs struct { + Recipient string `json:"recipient"` + Value string `json:"value"` + Gas string `json:"gas"` + GasPrice string `json:"gasprice"` + Data string `json:"data"` +} + +// type TxResponse struct { +// Hash string +// } + +func (a *NewTxArgs) requirements() error { + if a.Gas == "" { + return NewErrorResponse("Transact requires a 'gas' value as argument") + } + if a.GasPrice == "" { + return NewErrorResponse("Transact requires a 'gasprice' value as argument") + } + return nil +} + +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 NewErrorResponse(ErrorDecodeArgs) +} + +func (a *PushTxArgs) requirementsPushTx() error { + if a.Tx == "" { + return NewErrorResponse("PushTx requires a 'tx' as argument") + } + return nil +} + +type GetStorageArgs struct { + Address string + Key string +} + +func (obj *GetStorageArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, arg0); err == nil { + obj.Address = arg0 + return + } + return NewErrorResponse(ErrorDecodeArgs) +} + +func (a *GetStorageArgs) requirements() error { + if a.Address == "" { + return NewErrorResponse("GetStorageAt requires an 'address' value as argument") + } + if a.Key == "" { + return NewErrorResponse("GetStorageAt requires an 'key' value as argument") + } + return nil +} + +type GetStorageAtRes struct { + Key string `json:"key"` + Value string `json:"value"` + Address string `json:"address"` +} + +type GetTxCountArgs struct { + Address string `json:"address"` +} + +// type GetTxCountRes struct { +// Nonce int `json:"nonce"` +// } + +func (obj *GetTxCountArgs) UnmarshalJSON(b []byte) (err error) { + arg0 := "" + if err = json.Unmarshal(b, arg0); err == nil { + obj.Address = arg0 + return + } + return NewErrorResponse("Could not determine JSON parameters") +} + +func (a *GetTxCountArgs) requirements() error { + if a.Address == "" { + return NewErrorResponse("GetTxCountAt requires an 'address' value as argument") + } + return nil +} + +// type GetPeerCountRes struct { +// PeerCount int `json:"peerCount"` +// } + +// type GetListeningRes struct { +// IsListening bool `json:"isListening"` +// } + +// type GetCoinbaseRes struct { +// Coinbase string `json:"coinbase"` +// } + +// type GetMiningRes struct { +// IsMining bool `json:"isMining"` +// } + +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 NewErrorResponse("Could not determine JSON parameters") +} + +func (a *GetBalanceArgs) requirements() error { + if a.Address == "" { + return NewErrorResponse("GetBalanceAt requires an 'address' value as argument") + } + return nil +} + +type BalanceRes struct { + Balance string `json:"balance"` + Address string `json:"address"` +} + +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 NewErrorResponse(ErrorDecodeArgs) +} + +func (a *GetCodeAtArgs) requirements() error { + if a.Address == "" { + return NewErrorResponse("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 NewErrorResponse(ErrorDecodeArgs) + } + return +} diff --git a/rpc/http/server.go b/rpc/http/server.go new file mode 100644 index 000000000..965727a4e --- /dev/null +++ b/rpc/http/server.go @@ -0,0 +1,111 @@ +/* + 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(":%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 { + fn := func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + + rpchttplogger.Debugln("Handling request") + + reqParsed, reqerr := JSON.ParseRequestBody(req) + if reqerr != nil { + JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: reqParsed.JsonRpc, ID: reqParsed.ID, Error: true, ErrorText: rpc.ErrorParseRequest}) + return + } + + var response interface{} + reserr := api.GetRequestReply(&reqParsed, &response) + if reserr != nil { + rpchttplogger.Errorln(reserr) + JSON.Send(w, &rpc.RpcErrorResponse{JsonRpc: reqParsed.JsonRpc, ID: reqParsed.ID, Error: true, ErrorText: reserr.Error()}) + return + } + + rpchttplogger.Debugf("Generated response: %T %s", response, response) + JSON.Send(w, &rpc.RpcSuccessResponse{JsonRpc: reqParsed.JsonRpc, ID: reqParsed.ID, Error: false, Result: response}) + } + + return http.HandlerFunc(fn) +} diff --git a/rpc/json.go b/rpc/json.go index e467f9a34..9bd204818 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -1,20 +1,58 @@ +/* + 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" + "github.com/ethereum/go-ethereum/logger" "io" + "net/http" ) -type jsonWrapper struct{} +var rpclogger = logger.NewLogger("RPC") -func (self jsonWrapper) Send(writer io.Writer, v interface{}) (n int, err error) { +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.Infof("Sending payload: %s", payload) return writer.Write(payload) } -var JSON jsonWrapper +func (self JsonWrapper) ParseRequestBody(req *http.Request) (RpcRequest, error) { + var reqParsed RpcRequest + + // Convert JSON to native types + d := json.NewDecoder(req.Body) + // d.UseNumber() + 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 +} diff --git a/rpc/message.go b/rpc/message.go index c8103eded..e9f47634f 100644 --- a/rpc/message.go +++ b/rpc/message.go @@ -1,14 +1,186 @@ +/* + 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 "github.com/ethereum/go-ethereum/ethutil" +import ( + "bytes" + "encoding/json" + "errors" + "fmt" +) + +const ( + ErrorArguments = "Error: Insufficient arguments" + ErrorNotImplemented = "Error: Method not implemented" + ErrorUnknown = "Error: Unknown error" + ErrorParseRequest = "Error: Could not parse request" + ErrorDecodeArgs = "Error: Could not decode arguments" +) + +type ErrorResponse struct { + Error bool `json:"error"` + ErrorText string `json:"errorText"` +} + +type RpcSuccessResponse struct { + ID int `json:"id"` + JsonRpc string `json:"jsonrpc"` + Error bool `json:"error"` + Result interface{} `json:"result"` +} + +type RpcErrorResponse struct { + ID int `json:"id"` + JsonRpc string `json:"jsonrpc"` + Error bool `json:"error"` + ErrorText string `json:"errortext"` +} + +type RpcRequest struct { + JsonRpc string `json:"jsonrpc"` + ID int `json:"id"` + Method string `json:"method"` + Params []json.RawMessage `json:"params"` +} + +func NewErrorResponse(msg string) error { + return errors.New(msg) +} + +func NewErrorResponseWithError(msg string, err error) error { + return fmt.Errorf("%s: %v", msg, err) +} + +func (req *RpcRequest) ToSha3Args() (*Sha3Args, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(Sha3Args) + r := bytes.NewReader(req.Params[0]) + if err := json.NewDecoder(r).Decode(args); err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToGetBlockArgs() (*GetBlockArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(GetBlockArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToNewTxArgs() (*NewTxArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(NewTxArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponseWithError(ErrorDecodeArgs, err) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToPushTxArgs() (*PushTxArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(PushTxArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToGetStorageArgs() (*GetStorageArgs, error) { + if len(req.Params) < 2 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(GetStorageArgs) + // TODO need to pass both arguments + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToGetTxCountArgs() (*GetTxCountArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } -type Message struct { - Call string `json:"call"` - Args []interface{} `json:"args"` - Id int `json:"_id"` - Data interface{} `json:"data"` + args := new(GetTxCountArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil } -func (self *Message) Arguments() *ethutil.Value { - return ethutil.NewValue(self.Args) +func (req *RpcRequest) ToGetBalanceArgs() (*GetBalanceArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(GetBalanceArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil +} + +func (req *RpcRequest) ToGetCodeAtArgs() (*GetCodeAtArgs, error) { + if len(req.Params) < 1 { + return nil, NewErrorResponse(ErrorArguments) + } + + args := new(GetCodeAtArgs) + r := bytes.NewReader(req.Params[0]) + err := json.NewDecoder(r).Decode(args) + if err != nil { + return nil, NewErrorResponse(ErrorDecodeArgs) + } + rpclogger.DebugDetailf("%T %v", args, args) + return args, nil } diff --git a/rpc/packages.go b/rpc/packages.go index 5535e6e79..11a172bd6 100644 --- a/rpc/packages.go +++ b/rpc/packages.go @@ -1,204 +1,106 @@ +/* + 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() +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 ( - "encoding/json" - "errors" + "fmt" "math/big" "strings" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/xeth" ) -type EthereumApi struct { - pipe *xeth.JSXEth -} - -type JsonArgs interface { - requirements() error -} - -type BlockResponse struct { - JsonResponse -} -type GetBlockArgs struct { - BlockNumber int - Hash string +type RpcServer interface { + Start() + Stop() } -type ErrorResponse struct { - Error bool `json:"error"` - ErrorText string `json:"errorText"` +func NewEthereumApi(xeth *xeth.XEth) *EthereumApi { + return &EthereumApi{xeth: xeth} } -type JsonResponse interface { -} - -type SuccessRes struct { - Error bool `json:"error"` - Result JsonResponse `json:"result"` -} - -func NewSuccessRes(object JsonResponse) string { - e := SuccessRes{Error: false, Result: object} - res, err := json.Marshal(e) - if err != nil { - // This should never happen - panic("Creating json error response failed, help") - } - success := string(res) - return success -} - -func NewErrorResponse(msg string) error { - e := ErrorResponse{Error: true, ErrorText: msg} - res, err := json.Marshal(e) - if err != nil { - // This should never happen - panic("Creating json error response failed, help") - } - newErr := errors.New(string(res)) - return newErr -} - -func (b *GetBlockArgs) requirements() error { - if b.BlockNumber == 0 && b.Hash == "" { - return NewErrorResponse("GetBlock requires either a block 'number' or a block 'hash' as argument") - } - return nil +type EthereumApi struct { + xeth *xeth.XEth } -func (p *EthereumApi) GetBlock(args *GetBlockArgs, reply *string) error { +func (p *EthereumApi) GetBlock(args *GetBlockArgs, reply *interface{}) error { err := args.requirements() if err != nil { return err } - block := p.pipe.BlockByHash(args.Hash) - *reply = NewSuccessRes(block) - return nil -} - -type NewTxArgs struct { - Sec string - Recipient string - Value string - Gas string - GasPrice string - Init string - Body string -} -type TxResponse struct { - Hash string -} - -func (a *NewTxArgs) requirements() error { - if a.Recipient == "" { - return NewErrorResponse("Transact requires a 'recipient' address as argument") - } - if a.Value == "" { - return NewErrorResponse("Transact requires a 'value' as argument") - } - if a.Gas == "" { - return NewErrorResponse("Transact requires a 'gas' value as argument") - } - if a.GasPrice == "" { - return NewErrorResponse("Transact requires a 'gasprice' value as argument") - } - return nil -} - -func (a *NewTxArgs) requirementsContract() error { - if a.Value == "" { - return NewErrorResponse("Create requires a 'value' as argument") - } - if a.Gas == "" { - return NewErrorResponse("Create requires a 'gas' value as argument") - } - if a.GasPrice == "" { - return NewErrorResponse("Create requires a 'gasprice' value as argument") - } - if a.Body == "" { - return NewErrorResponse("Create requires a 'body' value as argument") + if args.BlockNumber > 0 { + *reply = p.xeth.BlockByNumber(args.BlockNumber) + } else { + *reply = p.xeth.BlockByHash(args.Hash) } return nil } -func (p *EthereumApi) Transact(args *NewTxArgs, reply *string) error { +func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) error { err := args.requirements() if err != nil { return err } - result, _ := p.pipe.Transact(p.pipe.Key().PrivateKey, args.Recipient, args.Value, args.Gas, args.GasPrice, args.Body) - *reply = NewSuccessRes(result) + result, _ := p.xeth.Transact( /* TODO specify account */ args.Recipient, args.Value, args.Gas, args.GasPrice, args.Data) + fmt.Println("result:", result) + *reply = result return nil } -func (p *EthereumApi) Create(args *NewTxArgs, reply *string) error { +func (p *EthereumApi) Create(args *NewTxArgs, reply *interface{}) error { err := args.requirementsContract() if err != nil { return err } - result, _ := p.pipe.Transact(p.pipe.Key().PrivateKey, "", args.Value, args.Gas, args.GasPrice, args.Body) - *reply = NewSuccessRes(result) + result, _ := p.xeth.Transact( /* TODO specify account */ "", args.Value, args.Gas, args.GasPrice, args.Data) + *reply = result return nil } -type PushTxArgs struct { - Tx string -} - -func (a *PushTxArgs) requirementsPushTx() error { - if a.Tx == "" { - return NewErrorResponse("PushTx requires a 'tx' as argument") - } - return nil -} - -func (p *EthereumApi) PushTx(args *PushTxArgs, reply *string) error { +func (p *EthereumApi) PushTx(args *PushTxArgs, reply *interface{}) error { err := args.requirementsPushTx() if err != nil { return err } - result, _ := p.pipe.PushTx(args.Tx) - *reply = NewSuccessRes(result) - return nil -} - -func (p *EthereumApi) GetKey(args interface{}, reply *string) error { - *reply = NewSuccessRes(p.pipe.Key()) - return nil -} - -type GetStorageArgs struct { - Address string - Key string -} - -func (a *GetStorageArgs) requirements() error { - if a.Address == "" { - return NewErrorResponse("GetStorageAt requires an 'address' value as argument") - } - if a.Key == "" { - return NewErrorResponse("GetStorageAt requires an 'key' value as argument") - } + result, _ := p.xeth.PushTx(args.Tx) + *reply = result return nil } -type GetStorageAtRes struct { - Key string `json:"key"` - Value string `json:"value"` - Address string `json:"address"` -} - -func (p *EthereumApi) GetStorageAt(args *GetStorageArgs, reply *string) error { +func (p *EthereumApi) GetStorageAt(args *GetStorageArgs, reply *interface{}) error { err := args.requirements() if err != nil { return err } - state := p.pipe.World().SafeGet(ethutil.Hex2Bytes(args.Address)) + state := p.xeth.State().SafeGet(args.Address) var hx string if strings.Index(args.Key, "0x") == 0 { @@ -208,104 +110,123 @@ func (p *EthereumApi) GetStorageAt(args *GetStorageArgs, reply *string) error { i, _ := new(big.Int).SetString(args.Key, 10) hx = ethutil.Bytes2Hex(i.Bytes()) } - jsonlogger.Debugf("GetStorageAt(%s, %s)\n", args.Address, hx) + rpclogger.Debugf("GetStorageAt(%s, %s)\n", args.Address, hx) value := state.Storage(ethutil.Hex2Bytes(hx)) - *reply = NewSuccessRes(GetStorageAtRes{Address: args.Address, Key: args.Key, Value: value.Str()}) - return nil -} - -type GetTxCountArgs struct { - Address string `json:"address"` -} -type GetTxCountRes struct { - Nonce int `json:"nonce"` -} - -func (a *GetTxCountArgs) requirements() error { - if a.Address == "" { - return NewErrorResponse("GetTxCountAt requires an 'address' value as argument") - } + *reply = GetStorageAtRes{Address: args.Address, Key: args.Key, Value: value.Str()} return nil } -type GetPeerCountRes struct { - PeerCount int `json:"peerCount"` -} - -func (p *EthereumApi) GetPeerCount(args *interface{}, reply *string) error { - *reply = NewSuccessRes(GetPeerCountRes{PeerCount: p.pipe.PeerCount()}) +func (p *EthereumApi) GetPeerCount(reply *interface{}) error { + *reply = p.xeth.PeerCount() return nil } -type GetListeningRes struct { - IsListening bool `json:"isListening"` -} - -func (p *EthereumApi) GetIsListening(args *interface{}, reply *string) error { - *reply = NewSuccessRes(GetListeningRes{IsListening: p.pipe.IsListening()}) +func (p *EthereumApi) GetIsListening(reply *interface{}) error { + *reply = p.xeth.IsListening() return nil } -type GetCoinbaseRes struct { - Coinbase string `json:"coinbase"` -} - -func (p *EthereumApi) GetCoinbase(args *interface{}, reply *string) error { - *reply = NewSuccessRes(GetCoinbaseRes{Coinbase: p.pipe.CoinBase()}) +func (p *EthereumApi) GetCoinbase(reply *interface{}) error { + *reply = p.xeth.Coinbase() return nil } -type GetMiningRes struct { - IsMining bool `json:"isMining"` -} - -func (p *EthereumApi) GetIsMining(args *interface{}, reply *string) error { - *reply = NewSuccessRes(GetMiningRes{IsMining: p.pipe.IsMining()}) +func (p *EthereumApi) GetIsMining(reply *interface{}) error { + *reply = p.xeth.IsMining() return nil } -func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *string) error { +func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *interface{}) error { err := args.requirements() if err != nil { return err } - state := p.pipe.TxCountAt(args.Address) - *reply = NewSuccessRes(GetTxCountRes{Nonce: state}) + *reply = p.xeth.TxCountAt(args.Address) return nil } -type GetBalanceArgs struct { - Address string -} - -func (a *GetBalanceArgs) requirements() error { - if a.Address == "" { - return NewErrorResponse("GetBalanceAt requires an 'address' value as argument") +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 = BalanceRes{Balance: state.Balance().String(), Address: args.Address} return nil } -type BalanceRes struct { - Balance string `json:"balance"` - Address string `json:"address"` -} - -func (p *EthereumApi) GetBalanceAt(args *GetBalanceArgs, reply *string) error { +func (p *EthereumApi) GetCodeAt(args *GetCodeAtArgs, reply *interface{}) error { err := args.requirements() if err != nil { return err } - state := p.pipe.World().SafeGet(ethutil.Hex2Bytes(args.Address)) - *reply = NewSuccessRes(BalanceRes{Balance: state.Balance().String(), Address: args.Address}) - return nil -} - -type TestRes struct { - JsonResponse `json:"-"` - Answer int `json:"answer"` -} - -func (p *EthereumApi) Test(args *GetBlockArgs, reply *string) error { - *reply = NewSuccessRes(TestRes{Answer: 15}) + *reply = p.xeth.CodeAt(args.Address) + return nil +} + +func (p *EthereumApi) Sha3(args *Sha3Args, reply *interface{}) error { + *reply = ethutil.Bytes2Hex(crypto.Sha3(ethutil.Hex2Bytes(args.Data))) + return nil +} + +func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error { + // Spec at https://github.com/ethereum/wiki/wiki/Generic-ON-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_peerCount": + return p.GetPeerCount(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.ToGetStorageArgs() + 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 "web3_sha3": + args, err := req.ToSha3Args() + if err != nil { + return err + } + return p.Sha3(args, reply) + default: + return NewErrorResponse(fmt.Sprintf("%v %s", ErrorNotImplemented, req.Method)) + } + + rpclogger.DebugDetailf("Reply: %T %s", reply, reply) return nil } diff --git a/rpc/server.go b/rpc/server.go deleted file mode 100644 index 983dc6c33..000000000 --- a/rpc/server.go +++ /dev/null @@ -1,67 +0,0 @@ -package rpc - -import ( - "fmt" - "net" - "net/rpc" - "net/rpc/jsonrpc" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/xeth" -) - -var jsonlogger = logger.NewLogger("JSON") - -type JsonRpcServer struct { - quit chan bool - listener net.Listener - pipe *xeth.JSXEth -} - -func (s *JsonRpcServer) exitHandler() { -out: - for { - select { - case <-s.quit: - s.listener.Close() - break out - } - } - - jsonlogger.Infoln("Shutdown JSON-RPC server") -} - -func (s *JsonRpcServer) Stop() { - close(s.quit) -} - -func (s *JsonRpcServer) Start() { - jsonlogger.Infoln("Starting JSON-RPC server") - go s.exitHandler() - rpc.Register(&EthereumApi{pipe: s.pipe}) - rpc.HandleHTTP() - - for { - conn, err := s.listener.Accept() - if err != nil { - jsonlogger.Infoln("Error starting JSON-RPC:", err) - break - } - jsonlogger.Debugln("Incoming request.") - go jsonrpc.ServeConn(conn) - } -} - -func NewJsonRpcServer(pipe *xeth.JSXEth, port int) (*JsonRpcServer, error) { - sport := fmt.Sprintf(":%d", port) - l, err := net.Listen("tcp", sport) - if err != nil { - return nil, err - } - - return &JsonRpcServer{ - listener: l, - quit: make(chan bool), - pipe: pipe, - }, nil -} diff --git a/rpc/writer.go b/rpc/writer.go deleted file mode 100644 index 4cc39f577..000000000 --- a/rpc/writer.go +++ /dev/null @@ -1,75 +0,0 @@ -package rpc - -/* -func pack(id int, v ...interface{}) Message { - return Message{Data: v, Id: id} -} - -func WriteOn(msg *Message, writer io.Writer) { - //msg := &Message{Seed: seed, Data: data} - - switch msg.Call { - case "compile": - data := ethutil.NewValue(msg.Args) - bcode, err := ethutil.Compile(data.Get(0).Str(), false) - if err != nil { - JSON.Send(writer, pack(msg.Id, err.Error())) - } - - code := ethutil.Bytes2Hex(bcode) - - JSON.Send(writer, pack(msg.Id, code, nil)) - case "block": - args := msg.Arguments() - - block := pipe.BlockByNumber(int32(args.Get(0).Uint())) - - JSON.Send(writer, pack(msg.Id, block)) - case "transact": - if mp, ok := msg.Args[0].(map[string]interface{}); ok { - object := mapToTxParams(mp) - JSON.Send( - writer, - pack(msg.Id, args(pipe.Transact(object["from"], object["to"], object["value"], object["gas"], object["gasPrice"], object["data"]))), - ) - - } - case "coinbase": - JSON.Send(writer, pack(msg.Id, pipe.CoinBase(), msg.Seed)) - - case "listening": - JSON.Send(writer, pack(msg.Id, pipe.IsListening())) - - case "mining": - JSON.Send(writer, pack(msg.Id, pipe.IsMining())) - - case "peerCoint": - JSON.Send(writer, pack(msg.Id, pipe.PeerCount())) - - case "countAt": - args := msg.Arguments() - - JSON.Send(writer, pack(msg.Id, pipe.TxCountAt(args.Get(0).Str()))) - - case "codeAt": - args := msg.Arguments() - - JSON.Send(writer, pack(msg.Id, len(pipe.CodeAt(args.Get(0).Str())))) - - case "stateAt": - args := msg.Arguments() - - JSON.Send(writer, pack(msg.Id, pipe.StorageAt(args.Get(0).Str(), args.Get(1).Str()))) - - case "balanceAt": - args := msg.Arguments() - - JSON.Send(writer, pack(msg.Id, pipe.BalanceAt(args.Get(0).Str()))) - - case "newFilter": - case "newFilterString": - case "messages": - // TODO - } -} -*/ diff --git a/rpc/ws/server.go b/rpc/ws/server.go new file mode 100644 index 000000000..37b08ebd9 --- /dev/null +++ b/rpc/ws/server.go @@ -0,0 +1,123 @@ +/* + 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 ws + +import ( + "fmt" + "net" + "net/http" + + "code.google.com/p/go.net/websocket" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/event/filter" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/xeth" +) + +var wslogger = logger.NewLogger("RPC-WS") + +type WebSocketServer struct { + eth *eth.Ethereum + filterManager *filter.FilterManager + port int + doneCh chan bool + listener net.Listener +} + +func NewWebSocketServer(eth *eth.Ethereum, port int) (*WebSocketServer, error) { + sport := fmt.Sprintf(":%d", port) + l, err := net.Listen("tcp", sport) + if err != nil { + return nil, err + } + + filterManager := filter.NewFilterManager(eth.EventMux()) + go filterManager.Start() + + return &WebSocketServer{eth, + filterManager, + 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(xeth.New(self.eth)) + 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 { + fn := func(conn *websocket.Conn) { + for { + wslogger.Debugln("Handling request") + var reqParsed rpc.RpcRequest + + if err := websocket.JSON.Receive(conn, &reqParsed); err != nil { + wslogger.Debugln(rpc.ErrorParseRequest) + websocket.JSON.Send(conn, rpc.RpcErrorResponse{JsonRpc: reqParsed.JsonRpc, ID: reqParsed.ID, Error: true, ErrorText: rpc.ErrorParseRequest}) + continue + } + + var response interface{} + reserr := api.GetRequestReply(&reqParsed, &response) + if reserr != nil { + wslogger.Errorln(reserr) + websocket.JSON.Send(conn, rpc.RpcErrorResponse{JsonRpc: reqParsed.JsonRpc, ID: reqParsed.ID, Error: true, ErrorText: reserr.Error()}) + continue + } + + wslogger.Debugf("Generated response: %T %s", response, response) + websocket.JSON.Send(conn, rpc.RpcSuccessResponse{JsonRpc: reqParsed.JsonRpc, ID: reqParsed.ID, Error: false, Result: response}) + } + } + return websocket.Handler(fn) +} |