diff --git a/cmd/utils/jeth.go b/cmd/utils/jeth.go index e5e520db20..e8b5d29f26 100644 --- a/cmd/utils/jeth.go +++ b/cmd/utils/jeth.go @@ -37,7 +37,7 @@ func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth { return &Jeth{re, client} } -func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (response otto.Value) { +func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { m := rpc.JSONErrResponse{ Version: "2.0", Id: id, diff --git a/rpc/json.go b/rpc/json.go index 1ed943c004..24d217adfa 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -24,6 +24,9 @@ import ( "strings" "sync/atomic" + "errors" + "strconv" + "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" ) @@ -40,14 +43,14 @@ const ( type JSONRequest struct { Method string `json:"method"` Version string `json:"jsonrpc"` - Id *int64 `json:"id,omitempty"` + Id json.RawMessage `json:"id"` Payload json.RawMessage `json:"params,omitempty"` } // JSON-RPC response type JSONSuccessResponse struct { Version string `json:"jsonrpc"` - Id int64 `json:"id"` + Id interface{} `json:"id,omitempty"` Result interface{} `json:"result"` } @@ -60,9 +63,9 @@ type JSONError struct { // JSON-RPC error response type JSONErrResponse struct { - Version string `json:"jsonrpc"` - Id *int64 `json:"id,omitempty"` - Error JSONError `json:"error"` + Version string `json:"jsonrpc"` + Id interface{} `json:"id,omitempty"` + Error JSONError `json:"error"` } // JSON-RPC notification payload @@ -123,21 +126,46 @@ func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { return parseRequest(incomingMsg) } +// checkId returns an error when req.Id isn't valid for RPC method calls +// according to the JSON-RPC 2.0 spec. +func checkId(req *JSONRequest) error { + if req.Id == nil { + return errors.New("RPC method expects request id") + } + + id := string(req.Id) + + if id == "null" { + return nil + } + + if _, err := strconv.ParseFloat(id, 64); err == nil { + return nil + } + + var str string + if err := json.Unmarshal(req.Id, &str); err == nil { + return nil + } + + return errors.New("invalid request id") +} + // parseRequest will parse a single request from the given RawMessage. It will return the parsed request, an indication // if the request was a batch or an error when the request could not be parsed. func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { - var in JSONRequest + var in *JSONRequest if err := json.Unmarshal(incomingMsg, &in); err != nil { return nil, false, &invalidMessageError{err.Error()} } - if in.Id == nil { - return nil, false, &invalidMessageError{"Server cannot handle notifications"} + if err := checkId(in); err != nil { + return nil, false, &invalidRequestError{err.Error()} } // subscribe are special, they will always use `subscribeMethod` as service method if in.Method == subscribeMethod { - reqs := []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true}} + reqs := []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true}} if len(in.Payload) > 0 { // first param must be subscription name var subscribeMethod [1]string @@ -155,7 +183,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { } if in.Method == unsubscribeMethod { - return []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true, + return []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true, method: unsubscribeMethod, params: in.Payload}}, false, nil } @@ -166,29 +194,29 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { } if len(in.Payload) == 0 { - return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id}}, false, nil + return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: &in.Id}}, false, nil } - return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id, params: in.Payload}}, false, nil + return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: &in.Id, params: in.Payload}}, false, nil } // parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication // if the request was a batch or an error when the request could not be read. func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { - var in []JSONRequest + var in []*JSONRequest if err := json.Unmarshal(incomingMsg, &in); err != nil { return nil, false, &invalidMessageError{err.Error()} } requests := make([]rpcRequest, len(in)) for i, r := range in { - if r.Id == nil { - return nil, true, &invalidMessageError{"Server cannot handle notifications"} + if err := checkId(r); err != nil { + return nil, true, &invalidRequestError{err.Error()} } // (un)subscribe are special, they will always use the same service.method if r.Method == subscribeMethod { - requests[i] = rpcRequest{id: *r.Id, isPubSub: true} + requests[i] = rpcRequest{id: &r.Id, isPubSub: true} if len(r.Payload) > 0 { var subscribeMethod [1]string if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil { @@ -206,7 +234,7 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro } if r.Method == unsubscribeMethod { - requests[i] = rpcRequest{id: *r.Id, isPubSub: true, method: unsubscribeMethod, params: r.Payload} + requests[i] = rpcRequest{id: &r.Id, isPubSub: true, method: unsubscribeMethod, params: r.Payload} continue } @@ -216,9 +244,9 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro } if len(r.Payload) == 0 { - requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: nil} + requests[i] = rpcRequest{service: elems[0], method: elems[1], id: &r.Id, params: nil} } else { - requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: r.Payload} + requests[i] = rpcRequest{service: elems[0], method: elems[1], id: &r.Id, params: r.Payload} } } @@ -294,7 +322,7 @@ func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([] } // CreateResponse will create a JSON-RPC success response with the given id and reply as result. -func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} { +func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} { if isHexNum(reflect.TypeOf(reply)) { return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)} } @@ -302,13 +330,13 @@ func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} { } // CreateErrorResponse will create a JSON-RPC error response with the given id and error. -func (c *jsonCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { +func (c *jsonCodec) CreateErrorResponse(id interface{}, err RPCError) interface{} { return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}} } // CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error. // info is optional and contains additional information about the error. When an empty string is passed it is ignored. -func (c *jsonCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { +func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} { return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}} } diff --git a/rpc/json_test.go b/rpc/json_test.go index 39aae1f54e..71d5fc3d09 100644 --- a/rpc/json_test.go +++ b/rpc/json_test.go @@ -51,8 +51,8 @@ func TestJSONRequestParsing(t *testing.T) { t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method) } - if requests[0].id != 1234 { - t.Fatalf("Expected id 1234 but got %d", requests[0].id) + if id, ok := requests[0].id.(float64); !ok || id != 1234 { + t.Fatalf("Expected id 1234 but got %v", requests[0].id) } var arg int diff --git a/rpc/server.go b/rpc/server.go index f42ee2d37a..3a7c821fc1 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -160,11 +160,11 @@ func (s *Server) ServeCodec(codec ServerCodec) { if batch { resps := make([]interface{}, len(reqs)) for i, r := range reqs { - resps[i] = codec.CreateErrorResponse(&r.id, err) + resps[i] = codec.CreateErrorResponse(r.id, err) } codec.Write(resps) } else { - codec.Write(codec.CreateErrorResponse(&reqs[0].id, err)) + codec.Write(codec.CreateErrorResponse(reqs[0].id, err)) } break } @@ -279,7 +279,7 @@ func (s *Server) unsubscribe(subid string) bool { // handle executes a request and returns the response from the callback. func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverRequest) interface{} { if req.err != nil { - return codec.CreateErrorResponse(&req.id, req.err) + return codec.CreateErrorResponse(req.id, req.err) } if req.isUnsubscribe { // first param must be the subscription id @@ -288,17 +288,17 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque if s.unsubscribe(subid) { return codec.CreateResponse(req.id, true) } else { - return codec.CreateErrorResponse(&req.id, + return codec.CreateErrorResponse(req.id, &callbackError{fmt.Sprintf("subscription '%s' not found", subid)}) } } - return codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as argument"}) + return codec.CreateErrorResponse(req.id, &invalidParamsError{"Expected subscription id as argument"}) } if req.callb.isSubscribe { subid, err := s.createSubscription(codec, req) if err != nil { - return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}) + return codec.CreateErrorResponse(req.id, &callbackError{err.Error()}) } return codec.CreateResponse(req.id, subid) } @@ -308,7 +308,7 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque rpcErr := &invalidParamsError{fmt.Sprintf("%s%s%s expects %d parameters, got %d", req.svcname, serviceMethodSeparator, req.callb.method.Name, len(req.callb.argTypes), len(req.args))} - return codec.CreateErrorResponse(&req.id, rpcErr) + return codec.CreateErrorResponse(req.id, rpcErr) } arguments := []reflect.Value{req.callb.rcvr} @@ -328,7 +328,7 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque if req.callb.errPos >= 0 { // test if method returned an error if !reply[req.callb.errPos].IsNil() { e := reply[req.callb.errPos].Interface().(error) - res := codec.CreateErrorResponse(&req.id, &callbackError{e.Error()}) + res := codec.CreateErrorResponse(req.id, &callbackError{e.Error()}) return res } } @@ -339,7 +339,7 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest) { var response interface{} if req.err != nil { - response = codec.CreateErrorResponse(&req.id, req.err) + response = codec.CreateErrorResponse(req.id, req.err) } else { response = s.handle(ctx, codec, req) } @@ -355,7 +355,7 @@ func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*s responses := make([]interface{}, len(requests)) for i, req := range requests { if req.err != nil { - responses[i] = codec.CreateErrorResponse(&req.id, req.err) + responses[i] = codec.CreateErrorResponse(req.id, req.err) } else { responses[i] = s.handle(ctx, codec, req) } diff --git a/rpc/server_test.go b/rpc/server_test.go index 5b91fe42ac..f10d3cde0b 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -109,7 +109,7 @@ func (c *ServerTestCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { if c.counter == 1 { var req JSONRequest json.Unmarshal(c.input, &req) - return []rpcRequest{rpcRequest{id: *req.Id, isPubSub: false, service: "test", method: req.Method, params: req.Payload}}, false, nil + return []rpcRequest{rpcRequest{id: req.Id, isPubSub: false, service: "test", method: req.Method, params: req.Payload}}, false, nil } // requests are executes in parallel, wait a bit before returning an error so that the previous request has time to @@ -172,15 +172,15 @@ func (c *ServerTestCodec) ParseRequestArguments(argTypes []reflect.Type, payload return argValues, nil } -func (c *ServerTestCodec) CreateResponse(id int64, reply interface{}) interface{} { +func (c *ServerTestCodec) CreateResponse(id interface{}, reply interface{}) interface{} { return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} } -func (c *ServerTestCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { +func (c *ServerTestCodec) CreateErrorResponse(id interface{}, err RPCError) interface{} { return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}} } -func (c *ServerTestCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { +func (c *ServerTestCodec) CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} { return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}} } diff --git a/rpc/types.go b/rpc/types.go index f268d84db2..ace2603540 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -57,7 +57,7 @@ type service struct { // serverRequest is an incoming request type serverRequest struct { - id int64 + id interface{} svcname string rcvr reflect.Value callb *callback @@ -86,7 +86,7 @@ type Server struct { type rpcRequest struct { service string method string - id int64 + id interface{} isPubSub bool params interface{} } @@ -108,11 +108,11 @@ type ServerCodec interface { // Parse request argument to the given types ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, RPCError) // Assemble success response - CreateResponse(int64, interface{}) interface{} + CreateResponse(interface{}, interface{}) interface{} // Assemble error response - CreateErrorResponse(*int64, RPCError) interface{} + CreateErrorResponse(interface{}, RPCError) interface{} // Assemble error response with extra information about the error through info - CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} + CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} // Create notification response CreateNotification(string, interface{}) interface{} // Write msg to client. diff --git a/rpc/utils.go b/rpc/utils.go index fa114284dd..92a4a408f1 100644 --- a/rpc/utils.go +++ b/rpc/utils.go @@ -218,7 +218,7 @@ func newSubscriptionId() (string, error) { // on which the given client connects. func SupportedModules(client Client) (map[string]string, error) { req := JSONRequest{ - Id: new(int64), + Id: []byte("1"), Version: "2.0", Method: "rpc_modules", }