diff --git a/Makefile b/Makefile index 3b005ea36..e17fa06ac 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,15 @@ -.PHONY: all test get_deps +PACKAGES=$(shell go list ./...) all: test -test: - bash ./test/test.sh +test: + @echo "--> Running go test --race" + @go test --race $(PACKAGES) + @echo "--> Running integration tests" + @bash ./test/integration_test.sh get_deps: - go get -t -u github.com/tendermint/go-rpc/... + @echo "--> Running go get" + @go get -v -d $(PACKAGES) + +.PHONY: all test get_deps diff --git a/README.md b/README.md index 1a91fb693..4da0e5634 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,16 @@ As a POST request, we use JSONRPC. For instance, the same request would have thi ``` { - "jsonrpc":"2.0", - "id":"anything", - "method":"hello_world", - "params":["my_world", 5] + "jsonrpc": "2.0", + "id": "anything", + "method": "hello_world", + "params": { + "name": "my_world", + "num": 5 + } } ``` -Note the `params` does not currently support key-value pairs (https://github.com/tendermint/go-rpc/issues/1), so order matters (you can get the order from making a -GET request to `/`) - With the above saved in file `data.json`, we can make the request with ``` @@ -50,8 +50,8 @@ curl --data @data.json http://localhost:8008 ## WebSocket (JSONRPC) -All requests are exposed over websocket in the same form as the POST JSONRPC. -Websocket connections are available at their own endpoint, typically `/websocket`, +All requests are exposed over websocket in the same form as the POST JSONRPC. +Websocket connections are available at their own endpoint, typically `/websocket`, though this is configurable when starting the server. # Server Definition @@ -102,7 +102,7 @@ go func() { Note that unix sockets are supported as well (eg. `/path/to/socket` instead of `0.0.0.0:8008`) Now see all available endpoints by sending a GET request to `0.0.0.0:8008`. -Each route is available as a GET request, as a JSONRPCv2 POST request, and via JSONRPCv2 over websockets +Each route is available as a GET request, as a JSONRPCv2 POST request, and via JSONRPCv2 over websockets. # Examples diff --git a/client/http_client.go b/client/http_client.go index 57da5d6ec..0c8bb6363 100644 --- a/client/http_client.go +++ b/client/http_client.go @@ -3,7 +3,6 @@ package rpcclient import ( "bytes" "encoding/json" - "errors" "fmt" "io/ioutil" "net" @@ -12,11 +11,16 @@ import ( "reflect" "strings" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-rpc/types" - "github.com/tendermint/go-wire" + // cmn "github.com/tendermint/go-common" + rpctypes "github.com/tendermint/go-rpc/types" + wire "github.com/tendermint/go-wire" ) +// HTTPClient is a common interface for ClientJSONRPC and ClientURI. +type HTTPClient interface { + Call(method string, params []interface{}, result interface{}) (interface{}, error) +} + // TODO: Deprecate support for IP:PORT or /path/to/socket func makeHTTPDialer(remoteAddr string) (string, func(string, string) (net.Conn, error)) { @@ -49,11 +53,6 @@ func makeHTTPClient(remoteAddr string) (string, *http.Client) { //------------------------------------------------------------------------------------ -type Client interface { -} - -//------------------------------------------------------------------------------------ - // JSON rpc takes params as a slice type ClientJSONRPC struct { address string @@ -68,11 +67,11 @@ func NewClientJSONRPC(remote string) *ClientJSONRPC { } } -func (c *ClientJSONRPC) Call(method string, params []interface{}, result interface{}) (interface{}, error) { +func (c *ClientJSONRPC) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { return c.call(method, params, result) } -func (c *ClientJSONRPC) call(method string, params []interface{}, result interface{}) (interface{}, error) { +func (c *ClientJSONRPC) call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { // Make request and get responseBytes request := rpctypes.RPCRequest{ JSONRPC: "2.0", @@ -80,7 +79,10 @@ func (c *ClientJSONRPC) call(method string, params []interface{}, result interfa Params: params, ID: "", } - requestBytes := wire.JSONBytes(request) + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } requestBuf := bytes.NewBuffer(requestBytes) // log.Info(Fmt("RPC request to %v (%v): %v", c.remote, method, string(requestBytes))) httpResponse, err := c.client.Post(c.address, "text/json", requestBuf) @@ -145,16 +147,16 @@ func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface response := &rpctypes.RPCResponse{} err = json.Unmarshal(responseBytes, response) if err != nil { - return nil, errors.New(Fmt("Error unmarshalling rpc response: %v", err)) + return nil, fmt.Errorf("Error unmarshalling rpc response: %v", err) } errorStr := response.Error if errorStr != "" { - return nil, errors.New(Fmt("Response error: %v", errorStr)) + return nil, fmt.Errorf("Response error: %v", errorStr) } // unmarshal the RawMessage into the result result = wire.ReadJSONPtr(result, *response.Result, &err) if err != nil { - return nil, errors.New(Fmt("Error unmarshalling rpc response result: %v", err)) + return nil, fmt.Errorf("Error unmarshalling rpc response result: %v", err) } return result, nil } diff --git a/client/ws_client.go b/client/ws_client.go index 4d975f8ec..e5135d0af 100644 --- a/client/ws_client.go +++ b/client/ws_client.go @@ -8,8 +8,8 @@ import ( "time" "github.com/gorilla/websocket" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-rpc/types" + cmn "github.com/tendermint/go-common" + rpctypes "github.com/tendermint/go-rpc/types" ) const ( @@ -19,7 +19,7 @@ const ( ) type WSClient struct { - BaseService + cmn.BaseService Address string // IP:PORT or /path/to/socket Endpoint string // /websocket/url/endpoint Dialer func(string, string) (net.Conn, error) @@ -39,7 +39,7 @@ func NewWSClient(remoteAddr, endpoint string) *WSClient { ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity), ErrorsCh: make(chan error, wsErrorsChannelCapacity), } - wsClient.BaseService = *NewBaseService(log, "WSClient", wsClient) + wsClient.BaseService = *cmn.NewBaseService(log, "WSClient", wsClient) return wsClient } @@ -122,7 +122,7 @@ func (wsc *WSClient) Subscribe(eventid string) error { JSONRPC: "2.0", ID: "", Method: "subscribe", - Params: []interface{}{eventid}, + Params: map[string]interface{}{"event": eventid}, }) return err } @@ -133,7 +133,7 @@ func (wsc *WSClient) Unsubscribe(eventid string) error { JSONRPC: "2.0", ID: "", Method: "unsubscribe", - Params: []interface{}{eventid}, + Params: map[string]interface{}{"event": eventid}, }) return err } diff --git a/rpc_test.go b/rpc_test.go index 1fcda0e5e..2c45c4c13 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-rpc/server" - "github.com/tendermint/go-rpc/types" - "github.com/tendermint/go-wire" + rpcclient "github.com/tendermint/go-rpc/client" + rpcserver "github.com/tendermint/go-rpc/server" + rpctypes "github.com/tendermint/go-rpc/types" + wire "github.com/tendermint/go-wire" ) // Client and Server should work over tcp or unix sockets @@ -88,7 +88,9 @@ func testURI(t *testing.T, cl *rpcclient.ClientURI) { func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) { val := "acbd" - params := []interface{}{val} + params := map[string]interface{}{ + "arg": val, + } var result Result _, err := cl.Call("status", params, &result) if err != nil { @@ -102,7 +104,9 @@ func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) { func testWS(t *testing.T, cl *rpcclient.WSClient) { val := "acbd" - params := []interface{}{val} + params := map[string]interface{}{ + "arg": val, + } err := cl.WriteJSON(rpctypes.RPCRequest{ JSONRPC: "2.0", ID: "", diff --git a/server/handlers.go b/server/handlers.go index 7a4ec1a45..beb664d9f 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" @@ -14,10 +13,10 @@ import ( "time" "github.com/gorilla/websocket" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-events" - . "github.com/tendermint/go-rpc/types" - "github.com/tendermint/go-wire" + cmn "github.com/tendermint/go-common" + events "github.com/tendermint/go-events" + types "github.com/tendermint/go-rpc/types" + wire "github.com/tendermint/go-wire" ) // Adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions. @@ -105,75 +104,99 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc { return } - var request RPCRequest + var request types.RPCRequest err := json.Unmarshal(b, &request) if err != nil { - WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error()))) return } if len(r.URL.Path) > 1 { - WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path))) + WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path))) return } rpcFunc := funcMap[request.Method] if rpcFunc == nil { - WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) + WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) return } if rpcFunc.ws { - WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method)) + WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method)) return } args, err := jsonParamsToArgs(rpcFunc, request.Params) if err != nil { - WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error()))) return } returns := rpcFunc.f.Call(args) log.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, result, fmt.Sprintf("Error unreflecting result: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, fmt.Sprintf("Error unreflecting result: %v", err.Error()))) return } - WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, result, "")) + WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, "")) } } // Convert a list of interfaces to properly typed values -func jsonParamsToArgs(rpcFunc *RPCFunc, params []interface{}) ([]reflect.Value, error) { +func jsonParamsToArgs(rpcFunc *RPCFunc, params map[string]interface{}) ([]reflect.Value, error) { if len(rpcFunc.argNames) != len(params) { - return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)", - len(rpcFunc.argNames), rpcFunc.argNames, len(params), params)) + return nil, fmt.Errorf("Expected %v parameters (%v), got %v (%v)", + len(rpcFunc.argNames), rpcFunc.argNames, len(params), params) } + values := make([]reflect.Value, len(params)) - for i, p := range params { + + for name, param := range params { + i := indexOf(name, rpcFunc.argNames) + if -1 == i { + return nil, fmt.Errorf("%s is not an argument (args: %v)", name, rpcFunc.argNames) + } ty := rpcFunc.args[i] - v, err := _jsonObjectToArg(ty, p) + v, err := _jsonObjectToArg(ty, param) if err != nil { return nil, err } values[i] = v } + return values, nil } +// indexOf returns index of a string in a slice of strings, -1 if not found. +func indexOf(value string, values []string) int { + for i, v := range values { + if v == value { + return i + } + } + return -1 +} + // Same as above, but with the first param the websocket connection -func jsonParamsToArgsWS(rpcFunc *RPCFunc, params []interface{}, wsCtx WSRPCContext) ([]reflect.Value, error) { +func jsonParamsToArgsWS(rpcFunc *RPCFunc, params map[string]interface{}, wsCtx types.WSRPCContext) ([]reflect.Value, error) { if len(rpcFunc.argNames) != len(params) { - return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)", - len(rpcFunc.argNames)-1, rpcFunc.argNames[1:], len(params), params)) + return nil, fmt.Errorf("Expected %v parameters (%v), got %v (%v)", + len(rpcFunc.argNames)-1, rpcFunc.argNames[1:], len(params), params) } + values := make([]reflect.Value, len(params)+1) values[0] = reflect.ValueOf(wsCtx) - for i, p := range params { + + for name, param := range params { + i := indexOf(name, rpcFunc.argNames) + if -1 == i { + return nil, fmt.Errorf("%s is not an argument (args: %v)", name, rpcFunc.argNames) + } ty := rpcFunc.args[i+1] - v, err := _jsonObjectToArg(ty, p) + v, err := _jsonObjectToArg(ty, param) if err != nil { return nil, err } values[i+1] = v } + return values, nil } @@ -197,7 +220,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request) // Exception for websocket endpoints if rpcFunc.ws { return func(w http.ResponseWriter, r *http.Request) { - WriteRPCResponseHTTP(w, NewRPCResponse("", nil, "This RPC method is only for websockets")) + WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, "This RPC method is only for websockets")) } } // All other endpoints @@ -205,17 +228,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request) log.Debug("HTTP HANDLER", "req", r) args, err := httpParamsToArgs(rpcFunc, r) if err != nil { - WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error()))) return } returns := rpcFunc.f.Call(args) log.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error unreflecting result: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error unreflecting result: %v", err.Error()))) return } - WriteRPCResponseHTTP(w, NewRPCResponse("", result, "")) + WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, "")) } } @@ -313,11 +336,11 @@ const ( // contains listener id, underlying ws connection, // and the event switch for subscribing to events type wsConnection struct { - BaseService + cmn.BaseService remoteAddr string baseConn *websocket.Conn - writeChan chan RPCResponse + writeChan chan types.RPCResponse readTimeout *time.Timer pingTicker *time.Ticker @@ -330,11 +353,11 @@ func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw wsc := &wsConnection{ remoteAddr: baseConn.RemoteAddr().String(), baseConn: baseConn, - writeChan: make(chan RPCResponse, writeChanCapacity), // error when full. + writeChan: make(chan types.RPCResponse, writeChanCapacity), // error when full. funcMap: funcMap, evsw: evsw, } - wsc.BaseService = *NewBaseService(log, "wsConnection", wsc) + wsc.BaseService = *cmn.NewBaseService(log, "wsConnection", wsc) return wsc } @@ -399,7 +422,7 @@ func (wsc *wsConnection) GetEventSwitch() events.EventSwitch { // Implements WSRPCConnection // Blocking write to writeChan until service stops. // Goroutine-safe -func (wsc *wsConnection) WriteRPCResponse(resp RPCResponse) { +func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) { select { case <-wsc.Quit: return @@ -410,7 +433,7 @@ func (wsc *wsConnection) WriteRPCResponse(resp RPCResponse) { // Implements WSRPCConnection // Nonblocking write. // Goroutine-safe -func (wsc *wsConnection) TryWriteRPCResponse(resp RPCResponse) bool { +func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool { select { case <-wsc.Quit: return false @@ -444,11 +467,11 @@ func (wsc *wsConnection) readRoutine() { wsc.Stop() return } - var request RPCRequest + var request types.RPCRequest err = json.Unmarshal(in, &request) if err != nil { errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error()) - wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, errStr)) + wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, errStr)) continue } @@ -456,28 +479,28 @@ func (wsc *wsConnection) readRoutine() { rpcFunc := wsc.funcMap[request.Method] if rpcFunc == nil { - wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) + wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) continue } var args []reflect.Value if rpcFunc.ws { - wsCtx := WSRPCContext{Request: request, WSRPCConnection: wsc} + wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc} args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx) } else { args, err = jsonParamsToArgs(rpcFunc, request.Params) } if err != nil { - wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, err.Error())) + wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) continue } returns := rpcFunc.f.Call(args) log.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, err.Error())) + wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) continue } else { - wsc.WriteRPCResponse(NewRPCResponse(request.ID, result, "")) + wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, "")) continue } diff --git a/test/data.json b/test/data.json index eac2e0dfe..83283ec33 100644 --- a/test/data.json +++ b/test/data.json @@ -1,6 +1,9 @@ { - "jsonrpc":"2.0", - "id":"", - "method":"hello_world", - "params":["my_world", 5] + "jsonrpc": "2.0", + "id": "", + "method": "hello_world", + "params": { + "name": "my_world", + "num": 5 + } } diff --git a/test/test.sh b/test/integration_test.sh similarity index 65% rename from test/test.sh rename to test/integration_test.sh index f5e740241..739708068 100755 --- a/test/test.sh +++ b/test/integration_test.sh @@ -1,17 +1,13 @@ -#! /bin/bash - -cd $GOPATH/src/github.com/tendermint/go-rpc - -# get deps -go get -u -t ./... - -# go tests -go test --race github.com/tendermint/go-rpc/... +#!/usr/bin/env bash +set -e +# Get the directory of where this script is. +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" -# integration tests -cd test -set -e +# Change into that dir because we expect that. +pushd "$DIR" go build -o server main.go ./server > /dev/null & @@ -19,8 +15,8 @@ PID=$! sleep 2 # simple request -R1=`curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5'` -R2=`curl -s --data @data.json http://localhost:8008` +R1=$(curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5') +R2=$(curl -s --data @data.json http://localhost:8008) if [[ "$R1" != "$R2" ]]; then echo "responses are not identical:" echo "R1: $R1" @@ -31,7 +27,7 @@ else fi # request with 0x-prefixed hex string arg -R1=`curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123'` +R1=$(curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123') R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi ABCD 123"},"error":""}' if [[ "$R1" != "$R2" ]]; then echo "responses are not identical:" @@ -43,7 +39,7 @@ else fi # request with unquoted string arg -R1=`curl -s 'http://localhost:8008/hello_world?name=abcd&num=123'` +R1=$(curl -s 'http://localhost:8008/hello_world?name=abcd&num=123') R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: invalid character 'a' looking for beginning of value\"}" if [[ "$R1" != "$R2" ]]; then echo "responses are not identical:" @@ -55,7 +51,7 @@ else fi # request with string type when expecting number arg -R1=`curl -s 'http://localhost:8008/hello_world?name="abcd"&num=0xabcd'` +R1=$(curl -s 'http://localhost:8008/hello_world?name="abcd"&num=0xabcd') R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: Got a hex string arg, but expected 'int'\"}" if [[ "$R1" != "$R2" ]]; then echo "responses are not identical:" @@ -67,3 +63,4 @@ else fi kill -9 $PID || exit 0 +popd diff --git a/test/main.go b/test/main.go index d14ab05f5..28de2be88 100644 --- a/test/main.go +++ b/test/main.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" rpcserver "github.com/tendermint/go-rpc/server" ) @@ -25,11 +25,11 @@ func main() { rpcserver.RegisterRPCFuncs(mux, routes) _, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux) if err != nil { - Exit(err.Error()) + cmn.Exit(err.Error()) } // Wait forever - TrapSignal(func() { + cmn.TrapSignal(func() { }) } diff --git a/types/types.go b/types/types.go index ee4a63cc8..cebd7564a 100644 --- a/types/types.go +++ b/types/types.go @@ -4,18 +4,18 @@ import ( "encoding/json" "strings" - "github.com/tendermint/go-events" - "github.com/tendermint/go-wire" + events "github.com/tendermint/go-events" + wire "github.com/tendermint/go-wire" ) type RPCRequest struct { - JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` - Method string `json:"method"` - Params []interface{} `json:"params"` + JSONRPC string `json:"jsonrpc"` + ID string `json:"id"` + Method string `json:"method"` + Params map[string]interface{} `json:"params"` } -func NewRPCRequest(id string, method string, params []interface{}) RPCRequest { +func NewRPCRequest(id string, method string, params map[string]interface{}) RPCRequest { return RPCRequest{ JSONRPC: "2.0", ID: id,