diff --git a/.changeset/odd-crews-approve.md b/.changeset/odd-crews-approve.md new file mode 100644 index 0000000000000000000000000000000000000000..5bff5e0d64c9ba86388f8f9d047faf5c790e7c50 --- /dev/null +++ b/.changeset/odd-crews-approve.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/proxyd': minor +--- + +Allows string RPC ids on proxyd diff --git a/go/proxyd/backend.go b/go/proxyd/backend.go index 7b1617b54669e7d1c56364ad4b15d2ea6d7d76fd..3df87b09b70197038a2e00b0a7519bc75d2f69f2 100644 --- a/go/proxyd/backend.go +++ b/go/proxyd/backend.go @@ -25,11 +25,6 @@ const ( ) var ( - ErrInvalidRequest = &RPCErr{ - Code: -32601, - Message: "invalid request", - HTTPErrorCode: 400, - } ErrParseErr = &RPCErr{ Code: -32700, Message: "parse error", @@ -67,6 +62,14 @@ var ( } ) +func ErrInvalidRequest(msg string) *RPCErr { + return &RPCErr{ + Code: -32601, + Message: msg, + HTTPErrorCode: 400, + } +} + type Backend struct { Name string rpcURL string @@ -498,7 +501,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { // just handle them here. req, err := w.prepareClientMsg(msg) if err != nil { - var id *int + var id json.RawMessage method := MethodUnknown if req != nil { id = req.ID @@ -555,7 +558,7 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { res, err := w.parseBackendMsg(msg) if err != nil { - var id *int + var id json.RawMessage if res != nil { id = res.ID } diff --git a/go/proxyd/rpc.go b/go/proxyd/rpc.go index 6b79671b0708bfeb289df449a9dd50f13406d562..54211c0a4e192ef06bdc3188580fe8e1b8c47b28 100644 --- a/go/proxyd/rpc.go +++ b/go/proxyd/rpc.go @@ -4,20 +4,21 @@ import ( "encoding/json" "io" "io/ioutil" + "strings" ) type RPCReq struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` Params json.RawMessage `json:"params"` - ID *int `json:"id"` + ID json.RawMessage `json:"id"` } type RPCRes struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - Error *RPCErr `json:"error,omitempty"` - ID *int `json:"id"` + JSONRPC string `json:"jsonrpc"` + Result interface{} `json:"result,omitempty"` + Error *RPCErr `json:"error,omitempty"` + ID json.RawMessage `json:"id"` } func (r *RPCRes) IsError() bool { @@ -34,6 +35,17 @@ func (r *RPCErr) Error() string { return r.Message } +func IsValidID(id json.RawMessage) bool { + // handle the case where the ID is a string + if strings.HasPrefix(string(id), "\"") && strings.HasSuffix(string(id), "\"") { + return len(id) > 2 + } + + // technically allows a boolean/null ID, but so does Geth + // https://github.com/ethereum/go-ethereum/blob/master/rpc/json.go#L72 + return len(id) > 0 && id[0] != '{' && id[0] != '[' +} + func ParseRPCReq(r io.Reader) (*RPCReq, error) { body, err := ioutil.ReadAll(r) if err != nil { @@ -46,11 +58,15 @@ func ParseRPCReq(r io.Reader) (*RPCReq, error) { } if req.JSONRPC != JSONRPCVersion { - return nil, ErrInvalidRequest + return nil, ErrInvalidRequest("invalid JSON-RPC version") } if req.Method == "" { - return nil, ErrInvalidRequest + return nil, ErrInvalidRequest("no method specified") + } + + if !IsValidID(req.ID) { + return nil, ErrInvalidRequest("invalid ID") } return req, nil @@ -70,7 +86,7 @@ func ParseRPCRes(r io.Reader) (*RPCRes, error) { return res, nil } -func NewRPCErrorRes(id *int, err error) *RPCRes { +func NewRPCErrorRes(id json.RawMessage, err error) *RPCRes { var rpcErr *RPCErr if rr, ok := err.(*RPCErr); ok { rpcErr = rr diff --git a/go/proxyd/server.go b/go/proxyd/server.go index 392add32640a52b28e7691518e621fdddf9c08d8..477e5302695af675f5e79fae74819b550feb30fa 100644 --- a/go/proxyd/server.go +++ b/go/proxyd/server.go @@ -232,7 +232,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ) } -func writeRPCError(w http.ResponseWriter, id *int, err error) { +func writeRPCError(w http.ResponseWriter, id json.RawMessage, err error) { var res *RPCRes if r, ok := err.(*RPCErr); ok { res = NewRPCErrorRes(id, r)