mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 06:42:16 +00:00
* Fixed accepting integer IDs in requests for Tendermint RPC server (#2366) * added a wrapper interface `jsonrpcid` that represents both string and int IDs in JSON-RPC requests/responses + custom JSON unmarshallers * changed client-side code in RPC that uses it * added extra tests for integer IDs * updated CHANGELOG_PENDING, as suggested by PR instructions * addressed PR comments * added table driven tests for request type marshalling/unmarshalling * expanded handler test to check IDs * changed pending changelog note * changed json rpc request/response unmarshalling to use empty interfaces and type switches on ID * some cleanup
This commit is contained in:
parent
b487feba42
commit
b12488b5f1
@ -33,3 +33,4 @@ program](https://hackerone.com/tendermint).
|
|||||||
### BUG FIXES:
|
### BUG FIXES:
|
||||||
|
|
||||||
- [rpc] \#2808 RPC validators calls IncrementAccum if necessary
|
- [rpc] \#2808 RPC validators calls IncrementAccum if necessary
|
||||||
|
- [rpc] \#2811 Allow integer IDs in JSON-RPC requests
|
||||||
|
@ -2,6 +2,7 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@ -104,7 +105,7 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscri
|
|||||||
go func() {
|
go func() {
|
||||||
for event := range ch {
|
for event := range ch {
|
||||||
tmResult := &ctypes.ResultEvent{query, event.(tmtypes.TMEventData)}
|
tmResult := &ctypes.ResultEvent{query, event.(tmtypes.TMEventData)}
|
||||||
wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Codec(), wsCtx.Request.ID+"#event", tmResult))
|
wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Codec(), rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", wsCtx.Request.ID)), tmResult))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ func NewJSONRPCClient(remote string) *JSONRPCClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
||||||
request, err := types.MapToRequest(c.cdc, "jsonrpc-client", method, params)
|
request, err := types.MapToRequest(c.cdc, types.JSONRPCStringID("jsonrpc-client"), method, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error {
|
|||||||
|
|
||||||
// Call the given method. See Send description.
|
// Call the given method. See Send description.
|
||||||
func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) error {
|
func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) error {
|
||||||
request, err := types.MapToRequest(c.cdc, "ws-client", method, params)
|
request, err := types.MapToRequest(c.cdc, types.JSONRPCStringID("ws-client"), method, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -224,7 +224,7 @@ func (c *WSClient) Call(ctx context.Context, method string, params map[string]in
|
|||||||
// CallWithArrayParams the given method with params in a form of array. See
|
// CallWithArrayParams the given method with params in a form of array. See
|
||||||
// Send description.
|
// Send description.
|
||||||
func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error {
|
func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error {
|
||||||
request, err := types.ArrayToRequest(c.cdc, "ws-client", method, params)
|
request, err := types.ArrayToRequest(c.cdc, types.JSONRPCStringID("ws-client"), method, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger lo
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
b, err := ioutil.ReadAll(r.Body)
|
b, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, types.RPCInvalidRequestError("", errors.Wrap(err, "Error reading request body")))
|
WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(types.JSONRPCStringID(""), errors.Wrap(err, "Error reading request body")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if its an empty request (like from a browser),
|
// if its an empty request (like from a browser),
|
||||||
@ -116,12 +116,12 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger lo
|
|||||||
var request types.RPCRequest
|
var request types.RPCRequest
|
||||||
err = json.Unmarshal(b, &request)
|
err = json.Unmarshal(b, &request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, types.RPCParseError("", errors.Wrap(err, "Error unmarshalling request")))
|
WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshalling request")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// A Notification is a Request object without an "id" member.
|
// A Notification is a Request object without an "id" member.
|
||||||
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
||||||
if request.ID == "" {
|
if request.ID == types.JSONRPCStringID("") {
|
||||||
logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -255,7 +255,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func
|
|||||||
// Exception for websocket endpoints
|
// Exception for websocket endpoints
|
||||||
if rpcFunc.ws {
|
if rpcFunc.ws {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(""))
|
WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(types.JSONRPCStringID("")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// All other endpoints
|
// All other endpoints
|
||||||
@ -263,17 +263,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func
|
|||||||
logger.Debug("HTTP HANDLER", "req", r)
|
logger.Debug("HTTP HANDLER", "req", r)
|
||||||
args, err := httpParamsToArgs(rpcFunc, cdc, r)
|
args, err := httpParamsToArgs(rpcFunc, cdc, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, types.RPCInvalidParamsError("", errors.Wrap(err, "Error converting http params to arguments")))
|
WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(types.JSONRPCStringID(""), errors.Wrap(err, "Error converting http params to arguments")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
returns := rpcFunc.f.Call(args)
|
returns := rpcFunc.f.Call(args)
|
||||||
logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
|
logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
|
||||||
result, err := unreflectResult(returns)
|
result, err := unreflectResult(returns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, types.RPCInternalError("", err))
|
WriteRPCResponseHTTP(w, types.RPCInternalError(types.JSONRPCStringID(""), err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, "", result))
|
WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, types.JSONRPCStringID(""), result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,7 +580,7 @@ func (wsc *wsConnection) readRoutine() {
|
|||||||
err = fmt.Errorf("WSJSONRPC: %v", r)
|
err = fmt.Errorf("WSJSONRPC: %v", r)
|
||||||
}
|
}
|
||||||
wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack()))
|
wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack()))
|
||||||
wsc.WriteRPCResponse(types.RPCInternalError("unknown", err))
|
wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCStringID("unknown"), err))
|
||||||
go wsc.readRoutine()
|
go wsc.readRoutine()
|
||||||
} else {
|
} else {
|
||||||
wsc.baseConn.Close() // nolint: errcheck
|
wsc.baseConn.Close() // nolint: errcheck
|
||||||
@ -615,13 +615,13 @@ func (wsc *wsConnection) readRoutine() {
|
|||||||
var request types.RPCRequest
|
var request types.RPCRequest
|
||||||
err = json.Unmarshal(in, &request)
|
err = json.Unmarshal(in, &request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wsc.WriteRPCResponse(types.RPCParseError("", errors.Wrap(err, "Error unmarshaling request")))
|
wsc.WriteRPCResponse(types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshaling request")))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Notification is a Request object without an "id" member.
|
// A Notification is a Request object without an "id" member.
|
||||||
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
|
||||||
if request.ID == "" {
|
if request.ID == types.JSONRPCStringID("") {
|
||||||
wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -47,21 +47,22 @@ func statusOK(code int) bool { return code >= 200 && code <= 299 }
|
|||||||
func TestRPCParams(t *testing.T) {
|
func TestRPCParams(t *testing.T) {
|
||||||
mux := testMux()
|
mux := testMux()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
payload string
|
payload string
|
||||||
wantErr string
|
wantErr string
|
||||||
|
expectedId interface{}
|
||||||
}{
|
}{
|
||||||
// bad
|
// bad
|
||||||
{`{"jsonrpc": "2.0", "id": "0"}`, "Method not found"},
|
{`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
|
||||||
{`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found"},
|
{`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")},
|
||||||
{`{"method": "c", "id": "0", "params": a}`, "invalid character"},
|
{`{"method": "c", "id": "0", "params": a}`, "invalid character", types.JSONRPCStringID("")}, // id not captured in JSON parsing failures
|
||||||
{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1"},
|
{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")},
|
||||||
{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character"},
|
{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")},
|
||||||
{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string"},
|
{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")},
|
||||||
|
|
||||||
// good
|
// good
|
||||||
{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, ""},
|
{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")},
|
||||||
{`{"method": "c", "id": "0", "params": {}}`, ""},
|
{`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")},
|
||||||
{`{"method": "c", "id": "0", "params": ["a", "10"]}`, ""},
|
{`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
@ -80,7 +81,7 @@ func TestRPCParams(t *testing.T) {
|
|||||||
recv := new(types.RPCResponse)
|
recv := new(types.RPCResponse)
|
||||||
assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
|
assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
|
||||||
assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
|
assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
|
||||||
|
assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
|
||||||
if tt.wantErr == "" {
|
if tt.wantErr == "" {
|
||||||
assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
|
assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
|
||||||
} else {
|
} else {
|
||||||
@ -91,9 +92,56 @@ func TestRPCParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJSONRPCID(t *testing.T) {
|
||||||
|
mux := testMux()
|
||||||
|
tests := []struct {
|
||||||
|
payload string
|
||||||
|
wantErr bool
|
||||||
|
expectedId interface{}
|
||||||
|
}{
|
||||||
|
// good id
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": null, "params": ["a", "10"]}`, false, nil},
|
||||||
|
|
||||||
|
// bad id
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil},
|
||||||
|
{`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload))
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
res := rec.Result()
|
||||||
|
// Always expecting back a JSONRPCResponse
|
||||||
|
assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i)
|
||||||
|
blob, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d: err reading body: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
recv := new(types.RPCResponse)
|
||||||
|
err = json.Unmarshal(blob, recv)
|
||||||
|
assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)
|
||||||
|
if !tt.wantErr {
|
||||||
|
assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i)
|
||||||
|
assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i)
|
||||||
|
assert.Nil(t, recv.Error, "#%d: not expecting an error", i)
|
||||||
|
} else {
|
||||||
|
assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRPCNotification(t *testing.T) {
|
func TestRPCNotification(t *testing.T) {
|
||||||
mux := testMux()
|
mux := testMux()
|
||||||
body := strings.NewReader(`{"jsonrpc": "2.0"}`)
|
body := strings.NewReader(`{"jsonrpc": "2.0", "id": ""}`)
|
||||||
req, _ := http.NewRequest("POST", "http://localhost/", body)
|
req, _ := http.NewRequest("POST", "http://localhost/", body)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
mux.ServeHTTP(rec, req)
|
mux.ServeHTTP(rec, req)
|
||||||
@ -134,7 +182,7 @@ func TestWebsocketManagerHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check basic functionality works
|
// check basic functionality works
|
||||||
req, err := types.MapToRequest(amino.NewCodec(), "TestWebsocketManager", "c", map[string]interface{}{"s": "a", "i": 10})
|
req, err := types.MapToRequest(amino.NewCodec(), types.JSONRPCStringID("TestWebsocketManager"), "c", map[string]interface{}{"s": "a", "i": 10})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = c.WriteJSON(req)
|
err = c.WriteJSON(req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -132,7 +132,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler
|
|||||||
"Panic in RPC HTTP handler", "err", e, "stack",
|
"Panic in RPC HTTP handler", "err", e, "stack",
|
||||||
string(debug.Stack()),
|
string(debug.Stack()),
|
||||||
)
|
)
|
||||||
WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError("", e.(error)))
|
WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError(types.JSONRPCStringID(""), e.(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -13,17 +14,75 @@ import (
|
|||||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// a wrapper to emulate a sum type: jsonrpcid = string | int
|
||||||
|
// TODO: refactor when Go 2.0 arrives https://github.com/golang/go/issues/19412
|
||||||
|
type jsonrpcid interface {
|
||||||
|
isJSONRPCID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONRPCStringID a wrapper for JSON-RPC string IDs
|
||||||
|
type JSONRPCStringID string
|
||||||
|
|
||||||
|
func (JSONRPCStringID) isJSONRPCID() {}
|
||||||
|
|
||||||
|
// JSONRPCIntID a wrapper for JSON-RPC integer IDs
|
||||||
|
type JSONRPCIntID int
|
||||||
|
|
||||||
|
func (JSONRPCIntID) isJSONRPCID() {}
|
||||||
|
|
||||||
|
func idFromInterface(idInterface interface{}) (jsonrpcid, error) {
|
||||||
|
switch id := idInterface.(type) {
|
||||||
|
case string:
|
||||||
|
return JSONRPCStringID(id), nil
|
||||||
|
case float64:
|
||||||
|
// json.Unmarshal uses float64 for all numbers
|
||||||
|
// (https://golang.org/pkg/encoding/json/#Unmarshal),
|
||||||
|
// but the JSONRPC2.0 spec says the id SHOULD NOT contain
|
||||||
|
// decimals - so we truncate the decimals here.
|
||||||
|
return JSONRPCIntID(int(id)), nil
|
||||||
|
default:
|
||||||
|
typ := reflect.TypeOf(id)
|
||||||
|
return nil, fmt.Errorf("JSON-RPC ID (%v) is of unknown type (%v)", id, typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
// REQUEST
|
// REQUEST
|
||||||
|
|
||||||
type RPCRequest struct {
|
type RPCRequest struct {
|
||||||
JSONRPC string `json:"jsonrpc"`
|
JSONRPC string `json:"jsonrpc"`
|
||||||
ID string `json:"id"`
|
ID jsonrpcid `json:"id"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
|
Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRPCRequest(id string, method string, params json.RawMessage) RPCRequest {
|
// UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int
|
||||||
|
func (request *RPCRequest) UnmarshalJSON(data []byte) error {
|
||||||
|
unsafeReq := &struct {
|
||||||
|
JSONRPC string `json:"jsonrpc"`
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(data, &unsafeReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.JSONRPC = unsafeReq.JSONRPC
|
||||||
|
request.Method = unsafeReq.Method
|
||||||
|
request.Params = unsafeReq.Params
|
||||||
|
if unsafeReq.ID == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id, err := idFromInterface(unsafeReq.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.ID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRPCRequest(id jsonrpcid, method string, params json.RawMessage) RPCRequest {
|
||||||
return RPCRequest{
|
return RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: id,
|
ID: id,
|
||||||
@ -36,7 +95,7 @@ func (req RPCRequest) String() string {
|
|||||||
return fmt.Sprintf("[%s %s]", req.ID, req.Method)
|
return fmt.Sprintf("[%s %s]", req.ID, req.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MapToRequest(cdc *amino.Codec, id string, method string, params map[string]interface{}) (RPCRequest, error) {
|
func MapToRequest(cdc *amino.Codec, id jsonrpcid, method string, params map[string]interface{}) (RPCRequest, error) {
|
||||||
var params_ = make(map[string]json.RawMessage, len(params))
|
var params_ = make(map[string]json.RawMessage, len(params))
|
||||||
for name, value := range params {
|
for name, value := range params {
|
||||||
valueJSON, err := cdc.MarshalJSON(value)
|
valueJSON, err := cdc.MarshalJSON(value)
|
||||||
@ -53,7 +112,7 @@ func MapToRequest(cdc *amino.Codec, id string, method string, params map[string]
|
|||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ArrayToRequest(cdc *amino.Codec, id string, method string, params []interface{}) (RPCRequest, error) {
|
func ArrayToRequest(cdc *amino.Codec, id jsonrpcid, method string, params []interface{}) (RPCRequest, error) {
|
||||||
var params_ = make([]json.RawMessage, len(params))
|
var params_ = make([]json.RawMessage, len(params))
|
||||||
for i, value := range params {
|
for i, value := range params {
|
||||||
valueJSON, err := cdc.MarshalJSON(value)
|
valueJSON, err := cdc.MarshalJSON(value)
|
||||||
@ -89,12 +148,38 @@ func (err RPCError) Error() string {
|
|||||||
|
|
||||||
type RPCResponse struct {
|
type RPCResponse struct {
|
||||||
JSONRPC string `json:"jsonrpc"`
|
JSONRPC string `json:"jsonrpc"`
|
||||||
ID string `json:"id"`
|
ID jsonrpcid `json:"id"`
|
||||||
Result json.RawMessage `json:"result,omitempty"`
|
Result json.RawMessage `json:"result,omitempty"`
|
||||||
Error *RPCError `json:"error,omitempty"`
|
Error *RPCError `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRPCSuccessResponse(cdc *amino.Codec, id string, res interface{}) RPCResponse {
|
// UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int
|
||||||
|
func (response *RPCResponse) UnmarshalJSON(data []byte) error {
|
||||||
|
unsafeResp := &struct {
|
||||||
|
JSONRPC string `json:"jsonrpc"`
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
Result json.RawMessage `json:"result,omitempty"`
|
||||||
|
Error *RPCError `json:"error,omitempty"`
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(data, &unsafeResp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response.JSONRPC = unsafeResp.JSONRPC
|
||||||
|
response.Error = unsafeResp.Error
|
||||||
|
response.Result = unsafeResp.Result
|
||||||
|
if unsafeResp.ID == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id, err := idFromInterface(unsafeResp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
response.ID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRPCSuccessResponse(cdc *amino.Codec, id jsonrpcid, res interface{}) RPCResponse {
|
||||||
var rawMsg json.RawMessage
|
var rawMsg json.RawMessage
|
||||||
|
|
||||||
if res != nil {
|
if res != nil {
|
||||||
@ -109,7 +194,7 @@ func NewRPCSuccessResponse(cdc *amino.Codec, id string, res interface{}) RPCResp
|
|||||||
return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg}
|
return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRPCErrorResponse(id string, code int, msg string, data string) RPCResponse {
|
func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse {
|
||||||
return RPCResponse{
|
return RPCResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: id,
|
ID: id,
|
||||||
@ -124,27 +209,27 @@ func (resp RPCResponse) String() string {
|
|||||||
return fmt.Sprintf("[%s %s]", resp.ID, resp.Error)
|
return fmt.Sprintf("[%s %s]", resp.ID, resp.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCParseError(id string, err error) RPCResponse {
|
func RPCParseError(id jsonrpcid, err error) RPCResponse {
|
||||||
return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error())
|
return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCInvalidRequestError(id string, err error) RPCResponse {
|
func RPCInvalidRequestError(id jsonrpcid, err error) RPCResponse {
|
||||||
return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error())
|
return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCMethodNotFoundError(id string) RPCResponse {
|
func RPCMethodNotFoundError(id jsonrpcid) RPCResponse {
|
||||||
return NewRPCErrorResponse(id, -32601, "Method not found", "")
|
return NewRPCErrorResponse(id, -32601, "Method not found", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCInvalidParamsError(id string, err error) RPCResponse {
|
func RPCInvalidParamsError(id jsonrpcid, err error) RPCResponse {
|
||||||
return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error())
|
return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCInternalError(id string, err error) RPCResponse {
|
func RPCInternalError(id jsonrpcid, err error) RPCResponse {
|
||||||
return NewRPCErrorResponse(id, -32603, "Internal error", err.Error())
|
return NewRPCErrorResponse(id, -32603, "Internal error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func RPCServerError(id string, err error) RPCResponse {
|
func RPCServerError(id jsonrpcid, err error) RPCResponse {
|
||||||
return NewRPCErrorResponse(id, -32000, "Server error", err.Error())
|
return NewRPCErrorResponse(id, -32000, "Server error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,24 +15,57 @@ type SampleResult struct {
|
|||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type responseTest struct {
|
||||||
|
id jsonrpcid
|
||||||
|
expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseTests = []responseTest{
|
||||||
|
{JSONRPCStringID("1"), `"1"`},
|
||||||
|
{JSONRPCStringID("alphabet"), `"alphabet"`},
|
||||||
|
{JSONRPCStringID(""), `""`},
|
||||||
|
{JSONRPCStringID("àáâ"), `"àáâ"`},
|
||||||
|
{JSONRPCIntID(-1), "-1"},
|
||||||
|
{JSONRPCIntID(0), "0"},
|
||||||
|
{JSONRPCIntID(1), "1"},
|
||||||
|
{JSONRPCIntID(100), "100"},
|
||||||
|
}
|
||||||
|
|
||||||
func TestResponses(t *testing.T) {
|
func TestResponses(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
cdc := amino.NewCodec()
|
cdc := amino.NewCodec()
|
||||||
|
for _, tt := range responseTests {
|
||||||
|
jsonid := tt.id
|
||||||
|
a := NewRPCSuccessResponse(cdc, jsonid, &SampleResult{"hello"})
|
||||||
|
b, _ := json.Marshal(a)
|
||||||
|
s := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"result":{"Value":"hello"}}`, tt.expected)
|
||||||
|
assert.Equal(string(s), string(b))
|
||||||
|
|
||||||
a := NewRPCSuccessResponse(cdc, "1", &SampleResult{"hello"})
|
d := RPCParseError(jsonid, errors.New("Hello world"))
|
||||||
b, _ := json.Marshal(a)
|
e, _ := json.Marshal(d)
|
||||||
s := `{"jsonrpc":"2.0","id":"1","result":{"Value":"hello"}}`
|
f := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32700,"message":"Parse error. Invalid JSON","data":"Hello world"}}`, tt.expected)
|
||||||
assert.Equal(string(s), string(b))
|
assert.Equal(string(f), string(e))
|
||||||
|
|
||||||
d := RPCParseError("1", errors.New("Hello world"))
|
g := RPCMethodNotFoundError(jsonid)
|
||||||
e, _ := json.Marshal(d)
|
h, _ := json.Marshal(g)
|
||||||
f := `{"jsonrpc":"2.0","id":"1","error":{"code":-32700,"message":"Parse error. Invalid JSON","data":"Hello world"}}`
|
i := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32601,"message":"Method not found"}}`, tt.expected)
|
||||||
assert.Equal(string(f), string(e))
|
assert.Equal(string(h), string(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g := RPCMethodNotFoundError("2")
|
func TestUnmarshallResponses(t *testing.T) {
|
||||||
h, _ := json.Marshal(g)
|
assert := assert.New(t)
|
||||||
i := `{"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"Method not found"}}`
|
cdc := amino.NewCodec()
|
||||||
assert.Equal(string(h), string(i))
|
for _, tt := range responseTests {
|
||||||
|
response := &RPCResponse{}
|
||||||
|
err := json.Unmarshal([]byte(fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"result":{"Value":"hello"}}`, tt.expected)), response)
|
||||||
|
assert.Nil(err)
|
||||||
|
a := NewRPCSuccessResponse(cdc, tt.id, &SampleResult{"hello"})
|
||||||
|
assert.Equal(*response, a)
|
||||||
|
}
|
||||||
|
response := &RPCResponse{}
|
||||||
|
err := json.Unmarshal([]byte(`{"jsonrpc":"2.0","id":true,"result":{"Value":"hello"}}`), response)
|
||||||
|
assert.NotNil(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRPCError(t *testing.T) {
|
func TestRPCError(t *testing.T) {
|
||||||
|
@ -191,7 +191,7 @@ func (t *transacter) sendLoop(connIndex int) {
|
|||||||
c.SetWriteDeadline(now.Add(sendTimeout))
|
c.SetWriteDeadline(now.Add(sendTimeout))
|
||||||
err = c.WriteJSON(rpctypes.RPCRequest{
|
err = c.WriteJSON(rpctypes.RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: "tm-bench",
|
ID: rpctypes.JSONRPCStringID("tm-bench"),
|
||||||
Method: t.BroadcastTxMethod,
|
Method: t.BroadcastTxMethod,
|
||||||
Params: rawParamsJSON,
|
Params: rawParamsJSON,
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user