Add support for JSON-RPC array payload. (#335)
This PR introduces solution for #333 - it adds support for array JSON-RPC payload. unmarshalMessage tries to unmarshal JSON paylod into *jsonrpcMessage object, and in case of failure, analyzes error and, if it's unmarshalling array error, tries to unmarshal it as an array.
This commit is contained in:
parent
bd2c3c6754
commit
9eee21f1ca
|
@ -3,6 +3,7 @@ package rpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/status-im/status-go/geth/log"
|
"github.com/status-im/status-go/geth/log"
|
||||||
|
@ -96,7 +97,6 @@ func methodAndParamsFromBody(body string) (string, []interface{}, json.RawMessag
|
||||||
if msg.Params != nil {
|
if msg.Params != nil {
|
||||||
err = json.Unmarshal(msg.Params, ¶ms)
|
err = json.Unmarshal(msg.Params, ¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("unmarshal params", "error", err)
|
|
||||||
return "", nil, nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,12 +104,44 @@ func methodAndParamsFromBody(body string) (string, []interface{}, json.RawMessag
|
||||||
return msg.Method, params, msg.ID, nil
|
return msg.Method, params, msg.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unmarshalMessage tries to unmarshal JSON-RPC message.
|
||||||
|
// somehow JSON-RPC input from web3.js can be in two forms:
|
||||||
|
//
|
||||||
|
// object: {"jsonrpc":"2.0", …}
|
||||||
|
// array: [{"jsonrpc":"2.0", …}]
|
||||||
|
//
|
||||||
|
// unmarhsalMessage tries first option and in case of error,
|
||||||
|
// tries to unmarshal it as an array.
|
||||||
|
//
|
||||||
|
// TODO(divan): fix the source of this error and cleanup.
|
||||||
func unmarshalMessage(body string) (*jsonrpcMessage, error) {
|
func unmarshalMessage(body string) (*jsonrpcMessage, error) {
|
||||||
var msg jsonrpcMessage
|
var msg jsonrpcMessage
|
||||||
err := json.Unmarshal([]byte(body), &msg)
|
err := json.Unmarshal([]byte(body), &msg)
|
||||||
|
// check for array case
|
||||||
|
if e, ok := err.(*json.UnmarshalTypeError); ok {
|
||||||
|
if e.Value == "array" {
|
||||||
|
return unmarshalMessageArray(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
return &msg, err
|
return &msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unmarshalMessageArray(body string) (*jsonrpcMessage, error) {
|
||||||
|
var msgs []*jsonrpcMessage
|
||||||
|
err := json.Unmarshal([]byte(body), &msgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// return first element
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
return nil, errors.New("empty array")
|
||||||
|
} else if len(msgs) > 1 {
|
||||||
|
log.Warn("JSON-RPC payload has more then 1 objects", "len", len(msgs), "body", body)
|
||||||
|
}
|
||||||
|
return msgs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
func newSuccessResponse(result json.RawMessage, id json.RawMessage) string {
|
func newSuccessResponse(result json.RawMessage, id json.RawMessage) string {
|
||||||
if id == nil {
|
if id == nil {
|
||||||
id = defaultMsgID
|
id = defaultMsgID
|
||||||
|
|
|
@ -50,11 +50,12 @@ func TestUnmarshalMessage(t *testing.T) {
|
||||||
|
|
||||||
func TestMethodAndParamsFromBody(t *testing.T) {
|
func TestMethodAndParamsFromBody(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
body string
|
body string
|
||||||
params []interface{}
|
params []interface{}
|
||||||
method string
|
method string
|
||||||
id json.RawMessage
|
id json.RawMessage
|
||||||
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"params_array",
|
"params_array",
|
||||||
|
@ -67,30 +68,60 @@ func TestMethodAndParamsFromBody(t *testing.T) {
|
||||||
},
|
},
|
||||||
"subtract",
|
"subtract",
|
||||||
json.RawMessage(`42`),
|
json.RawMessage(`42`),
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"params_empty_array",
|
"params_empty_array",
|
||||||
`{"jsonrpc": "2.0", "method": "test", "params": []}`,
|
`{"jsonrpc": "2.0", "method": "test", "params": []}`,
|
||||||
[]interface{}{},
|
[]interface{}{},
|
||||||
"test",
|
"test",
|
||||||
json.RawMessage(nil),
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"params_none",
|
"params_none",
|
||||||
`{"jsonrpc": "2.0", "method": "test"}`,
|
`{"jsonrpc": "2.0", "method": "test"}`,
|
||||||
[]interface{}{},
|
[]interface{}{},
|
||||||
"test",
|
"test",
|
||||||
json.RawMessage(nil),
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"getFilterMessage",
|
||||||
|
`{"jsonrpc":"2.0","id":44,"method":"shh_getFilterMessages","params":["3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e"]}`,
|
||||||
|
[]interface{}{string("3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e")},
|
||||||
|
"shh_getFilterMessages",
|
||||||
|
json.RawMessage(`44`),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"getFilterMessage_array",
|
||||||
|
`[{"jsonrpc":"2.0","id":44,"method":"shh_getFilterMessages","params":["3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e"]}]`,
|
||||||
|
[]interface{}{string("3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e")},
|
||||||
|
"shh_getFilterMessages",
|
||||||
|
json.RawMessage(`44`),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty_array",
|
||||||
|
`[]`,
|
||||||
|
[]interface{}{},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range cases {
|
for _, test := range cases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
method, params, id, err := methodAndParamsFromBody(test.body)
|
method, params, id, err := methodAndParamsFromBody(test.body)
|
||||||
|
if test.shouldFail {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, test.method, method)
|
require.Equal(t, test.method, method)
|
||||||
require.Equal(t, test.params, params)
|
require.Equal(t, test.params, params)
|
||||||
require.Equal(t, test.id, id)
|
require.EqualValues(t, test.id, id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/status-im/status-go/geth/log"
|
|
||||||
"github.com/status-im/status-go/geth/node"
|
"github.com/status-im/status-go/geth/node"
|
||||||
"github.com/status-im/status-go/geth/params"
|
"github.com/status-im/status-go/geth/params"
|
||||||
"github.com/status-im/status-go/geth/rpc"
|
"github.com/status-im/status-go/geth/rpc"
|
||||||
|
@ -183,7 +182,6 @@ func (s *RPCTestSuite) TestCallRPC() {
|
||||||
"id": 1
|
"id": 1
|
||||||
}`,
|
}`,
|
||||||
func(resultJSON string) {
|
func(resultJSON string) {
|
||||||
log.Info("eth_sendTransaction")
|
|
||||||
progress <- struct{}{}
|
progress <- struct{}{}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue