From f330f2f40b4074d7a1cd053ab1640f8d70913ce9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 14 Jan 2020 15:02:38 +0300 Subject: [PATCH] rpc: separate out request and response structures Mostly as is, no real effort done yet to optimize them, so there are still a lot of duplicates there, but at least we sort them out into different smaller packages. --- cli/smartcontract/smart_contract.go | 12 +- pkg/rpc/client.go | 13 +- pkg/rpc/request.go | 128 -------------- pkg/rpc/{ => request}/param.go | 19 ++- pkg/rpc/{ => request}/param_test.go | 62 +++---- pkg/rpc/{ => request}/params.go | 2 +- pkg/rpc/{ => request}/stack_param.go | 2 +- pkg/rpc/{ => request}/stack_param_test.go | 2 +- pkg/rpc/request/types.go | 84 +++++++++ pkg/rpc/{ => response}/errors.go | 23 +-- pkg/rpc/{ => response}/types.go | 105 +++++------- pkg/rpc/rpc.go | 62 +++---- pkg/rpc/scdetails.go | 6 +- pkg/rpc/server.go | 198 +++++++++++++++------- pkg/rpc/server_helper_test.go | 11 +- pkg/rpc/server_test.go | 3 +- pkg/rpc/txBuilder.go | 27 +-- pkg/rpc/tx_builder_test.go | 59 +++---- 18 files changed, 422 insertions(+), 396 deletions(-) delete mode 100644 pkg/rpc/request.go rename pkg/rpc/{ => request}/param.go (94%) rename pkg/rpc/{ => request}/param_test.go (76%) rename pkg/rpc/{ => request}/params.go (97%) rename pkg/rpc/{ => request}/stack_param.go (99%) rename pkg/rpc/{ => request}/stack_param_test.go (99%) create mode 100644 pkg/rpc/request/types.go rename pkg/rpc/{ => response}/errors.go (67%) rename pkg/rpc/{ => response}/types.go (52%) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 38e1d0901..b3e31a028 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -15,6 +15,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/rpc" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" @@ -294,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error { // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. if !ctx.Bool("skip-details") { details := parseContractDetails() - details.ReturnType = rpc.ByteArray - details.Parameters = make([]rpc.StackParamType, 2) - details.Parameters[0] = rpc.String - details.Parameters[1] = rpc.Array + details.ReturnType = request.ByteArray + details.Parameters = make([]request.StackParamType, 2) + details.Parameters[0] = request.String + details.Parameters[1] = request.Array project := &ProjectConfig{Contract: details} b, err := yaml.Marshal(project) @@ -362,7 +364,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { operation string params = make([]smartcontract.Parameter, 0) paramsStart = 1 - resp *rpc.InvokeScriptResponse + resp *response.InvokeScript wif *keys.WIF ) diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index e8a75bc81..167c7c8bf 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -14,6 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -177,13 +178,13 @@ func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.F } -func (c *Client) performRequest(method string, p params, v interface{}) error { +func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error { var ( - r = request{ - JSONRPC: c.version, - Method: method, - Params: p.values, - ID: 1, + r = request.Raw{ + JSONRPC: c.version, + Method: method, + RawParams: p.Values, + ID: 1, } buf = new(bytes.Buffer) ) diff --git a/pkg/rpc/request.go b/pkg/rpc/request.go deleted file mode 100644 index 5a3c21b9a..000000000 --- a/pkg/rpc/request.go +++ /dev/null @@ -1,128 +0,0 @@ -package rpc - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/pkg/errors" - "go.uber.org/zap" -) - -const ( - jsonRPCVersion = "2.0" -) - -type ( - // Request represents a standard JSON-RPC 2.0 - // request: http://www.jsonrpc.org/specification#request_object. - Request struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - RawParams json.RawMessage `json:"params,omitempty"` - RawID json.RawMessage `json:"id,omitempty"` - } - - // Response represents a standard JSON-RPC 2.0 - // response: http://www.jsonrpc.org/specification#response_object. - Response struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - Error *Error `json:"error,omitempty"` - ID json.RawMessage `json:"id,omitempty"` - } -) - -// NewRequest creates a new Request struct. -func NewRequest() *Request { - return &Request{ - JSONRPC: jsonRPCVersion, - } -} - -// DecodeData decodes the given reader into the the request -// struct. -func (r *Request) DecodeData(data io.ReadCloser) error { - defer data.Close() - - err := json.NewDecoder(data).Decode(r) - if err != nil { - return errors.Errorf("error parsing JSON payload: %s", err) - } - - if r.JSONRPC != jsonRPCVersion { - return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC) - } - - return nil -} - -// Params takes a slice of any type and attempts to bind -// the params to it. -func (r *Request) Params() (*Params, error) { - params := Params{} - - err := json.Unmarshal(r.RawParams, ¶ms) - if err != nil { - return nil, errors.Errorf("error parsing params field in payload: %s", err) - } - - return ¶ms, nil -} - -// WriteErrorResponse writes an error response to the ResponseWriter. -func (s *Server) WriteErrorResponse(r *Request, w http.ResponseWriter, err error) { - jsonErr, ok := err.(*Error) - if !ok { - jsonErr = NewInternalServerError("Internal server error", err) - } - - response := Response{ - JSONRPC: r.JSONRPC, - Error: jsonErr, - ID: r.RawID, - } - - logFields := []zap.Field{ - zap.Error(jsonErr.Cause), - zap.String("method", r.Method), - } - - params, err := r.Params() - if err == nil { - logFields = append(logFields, zap.Any("params", params)) - } - - s.log.Error("Error encountered with rpc request", logFields...) - - w.WriteHeader(jsonErr.HTTPCode) - s.writeServerResponse(r, w, response) -} - -// WriteResponse encodes the response and writes it to the ResponseWriter. -func (s *Server) WriteResponse(r *Request, w http.ResponseWriter, result interface{}) { - response := Response{ - JSONRPC: r.JSONRPC, - Result: result, - ID: r.RawID, - } - - s.writeServerResponse(r, w, response) -} - -func (s *Server) writeServerResponse(r *Request, w http.ResponseWriter, response Response) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - if s.config.EnableCORSWorkaround { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") - } - - encoder := json.NewEncoder(w) - err := encoder.Encode(response) - - if err != nil { - s.log.Error("Error encountered while encoding response", - zap.String("err", err.Error()), - zap.String("method", r.Method)) - } -} diff --git a/pkg/rpc/param.go b/pkg/rpc/request/param.go similarity index 94% rename from pkg/rpc/param.go rename to pkg/rpc/request/param.go index 1dc57eb07..e9f453175 100644 --- a/pkg/rpc/param.go +++ b/pkg/rpc/request/param.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "bytes" @@ -29,12 +29,13 @@ type ( } ) +// These are parameter types accepted by RPC server. const ( defaultT paramType = iota - stringT - numberT - arrayT - funcParamT + StringT + NumberT + ArrayT + FuncParamT ) func (p Param) String() string { @@ -123,7 +124,7 @@ func (p Param) GetBytesHex() ([]byte, error) { func (p *Param) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err == nil { - p.Type = stringT + p.Type = StringT p.Value = s return nil @@ -131,7 +132,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { var num float64 if err := json.Unmarshal(data, &num); err == nil { - p.Type = numberT + p.Type = NumberT p.Value = int(num) return nil @@ -142,7 +143,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { jd.DisallowUnknownFields() var fp FuncParam if err := jd.Decode(&fp); err == nil { - p.Type = funcParamT + p.Type = FuncParamT p.Value = fp return nil @@ -150,7 +151,7 @@ func (p *Param) UnmarshalJSON(data []byte) error { var ps []Param if err := json.Unmarshal(data, &ps); err == nil { - p.Type = arrayT + p.Type = ArrayT p.Value = ps return nil diff --git a/pkg/rpc/param_test.go b/pkg/rpc/request/param_test.go similarity index 76% rename from pkg/rpc/param_test.go rename to pkg/rpc/request/param_test.go index 58cc9b879..127fab9ad 100644 --- a/pkg/rpc/param_test.go +++ b/pkg/rpc/request/param_test.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/hex" @@ -15,35 +15,35 @@ func TestParam_UnmarshalJSON(t *testing.T) { msg := `["str1", 123, ["str2", 3], [{"type": "String", "value": "jajaja"}]]` expected := Params{ { - Type: stringT, + Type: StringT, Value: "str1", }, { - Type: numberT, + Type: NumberT, Value: 123, }, { - Type: arrayT, + Type: ArrayT, Value: []Param{ { - Type: stringT, + Type: StringT, Value: "str2", }, { - Type: numberT, + Type: NumberT, Value: 3, }, }, }, { - Type: arrayT, + Type: ArrayT, Value: []Param{ { - Type: funcParamT, + Type: FuncParamT, Value: FuncParam{ Type: String, Value: Param{ - Type: stringT, + Type: StringT, Value: "jajaja", }, }, @@ -61,34 +61,34 @@ func TestParam_UnmarshalJSON(t *testing.T) { } func TestParamGetString(t *testing.T) { - p := Param{stringT, "jajaja"} + p := Param{StringT, "jajaja"} str, err := p.GetString() assert.Equal(t, "jajaja", str) require.Nil(t, err) - p = Param{stringT, int(100500)} + p = Param{StringT, int(100500)} _, err = p.GetString() require.NotNil(t, err) } func TestParamGetInt(t *testing.T) { - p := Param{numberT, int(100500)} + p := Param{NumberT, int(100500)} i, err := p.GetInt() assert.Equal(t, 100500, i) require.Nil(t, err) - p = Param{numberT, "jajaja"} + p = Param{NumberT, "jajaja"} _, err = p.GetInt() require.NotNil(t, err) } func TestParamGetArray(t *testing.T) { - p := Param{arrayT, []Param{{numberT, 42}}} + p := Param{ArrayT, []Param{{NumberT, 42}}} a, err := p.GetArray() - assert.Equal(t, []Param{{numberT, 42}}, a) + assert.Equal(t, []Param{{NumberT, 42}}, a) require.Nil(t, err) - p = Param{arrayT, 42} + p = Param{ArrayT, 42} _, err = p.GetArray() require.NotNil(t, err) } @@ -96,16 +96,16 @@ func TestParamGetArray(t *testing.T) { func TestParamGetUint256(t *testing.T) { gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" u256, _ := util.Uint256DecodeStringLE(gas) - p := Param{stringT, gas} + p := Param{StringT, gas} u, err := p.GetUint256() assert.Equal(t, u256, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint256() require.NotNil(t, err) - p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} _, err = p.GetUint256() require.NotNil(t, err) } @@ -113,16 +113,16 @@ func TestParamGetUint256(t *testing.T) { func TestParamGetUint160FromHex(t *testing.T) { in := "50befd26fdf6e4d957c11e078b24ebce6291456f" u160, _ := util.Uint160DecodeStringLE(in) - p := Param{stringT, in} + p := Param{StringT, in} u, err := p.GetUint160FromHex() assert.Equal(t, u160, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint160FromHex() require.NotNil(t, err) - p = Param{stringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} + p = Param{StringT, "wwbefd26fdf6e4d957c11e078b24ebce6291456f"} _, err = p.GetUint160FromHex() require.NotNil(t, err) } @@ -130,16 +130,16 @@ func TestParamGetUint160FromHex(t *testing.T) { func TestParamGetUint160FromAddress(t *testing.T) { in := "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y" u160, _ := address.StringToUint160(in) - p := Param{stringT, in} + p := Param{StringT, in} u, err := p.GetUint160FromAddress() assert.Equal(t, u160, u) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetUint160FromAddress() require.NotNil(t, err) - p = Param{stringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} + p = Param{StringT, "QK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"} _, err = p.GetUint160FromAddress() require.NotNil(t, err) } @@ -148,19 +148,19 @@ func TestParamGetFuncParam(t *testing.T) { fp := FuncParam{ Type: String, Value: Param{ - Type: stringT, + Type: StringT, Value: "jajaja", }, } p := Param{ - Type: funcParamT, + Type: FuncParamT, Value: fp, } newfp, err := p.GetFuncParam() assert.Equal(t, fp, newfp) require.Nil(t, err) - p = Param{funcParamT, 42} + p = Param{FuncParamT, 42} _, err = p.GetFuncParam() require.NotNil(t, err) } @@ -168,16 +168,16 @@ func TestParamGetFuncParam(t *testing.T) { func TestParamGetBytesHex(t *testing.T) { in := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" inb, _ := hex.DecodeString(in) - p := Param{stringT, in} + p := Param{StringT, in} bh, err := p.GetBytesHex() assert.Equal(t, inb, bh) require.Nil(t, err) - p = Param{stringT, 42} + p = Param{StringT, 42} _, err = p.GetBytesHex() require.NotNil(t, err) - p = Param{stringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} + p = Param{StringT, "qq2c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"} _, err = p.GetBytesHex() require.NotNil(t, err) } diff --git a/pkg/rpc/params.go b/pkg/rpc/request/params.go similarity index 97% rename from pkg/rpc/params.go rename to pkg/rpc/request/params.go index 1affcf267..8b1945cb1 100644 --- a/pkg/rpc/params.go +++ b/pkg/rpc/request/params.go @@ -1,4 +1,4 @@ -package rpc +package request type ( // Params represents the JSON-RPC params. diff --git a/pkg/rpc/stack_param.go b/pkg/rpc/request/stack_param.go similarity index 99% rename from pkg/rpc/stack_param.go rename to pkg/rpc/request/stack_param.go index 6c9a7b5e2..192a2f0ef 100644 --- a/pkg/rpc/stack_param.go +++ b/pkg/rpc/request/stack_param.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/binary" diff --git a/pkg/rpc/stack_param_test.go b/pkg/rpc/request/stack_param_test.go similarity index 99% rename from pkg/rpc/stack_param_test.go rename to pkg/rpc/request/stack_param_test.go index dfd289e19..2d2029cff 100644 --- a/pkg/rpc/stack_param_test.go +++ b/pkg/rpc/request/stack_param_test.go @@ -1,4 +1,4 @@ -package rpc +package request import ( "encoding/json" diff --git a/pkg/rpc/request/types.go b/pkg/rpc/request/types.go new file mode 100644 index 000000000..9020d508e --- /dev/null +++ b/pkg/rpc/request/types.go @@ -0,0 +1,84 @@ +package request + +import ( + "encoding/json" + "io" + + "github.com/pkg/errors" +) + +const ( + // JSONRPCVersion is the only JSON-RPC protocol version supported. + JSONRPCVersion = "2.0" +) + +// RawParams is just a slice of abstract values, used to represent parameters +// passed from client to server. +type RawParams struct { + Values []interface{} +} + +// NewRawParams creates RawParams from its parameters. +func NewRawParams(vals ...interface{}) RawParams { + p := RawParams{} + p.Values = make([]interface{}, len(vals)) + for i := 0; i < len(p.Values); i++ { + p.Values[i] = vals[i] + } + return p +} + +// Raw represents JSON-RPC request. +type Raw struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + RawParams []interface{} `json:"params"` + ID int `json:"id"` +} + +// In represents a standard JSON-RPC 2.0 +// request: http://www.jsonrpc.org/specification#request_object. It's used in +// server to represent incoming queries. +type In struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + RawParams json.RawMessage `json:"params,omitempty"` + RawID json.RawMessage `json:"id,omitempty"` +} + +// NewIn creates a new Request struct. +func NewIn() *In { + return &In{ + JSONRPC: JSONRPCVersion, + } +} + +// DecodeData decodes the given reader into the the request +// struct. +func (r *In) DecodeData(data io.ReadCloser) error { + defer data.Close() + + err := json.NewDecoder(data).Decode(r) + if err != nil { + return errors.Errorf("error parsing JSON payload: %s", err) + } + + if r.JSONRPC != JSONRPCVersion { + return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC) + } + + return nil +} + +// Params takes a slice of any type and attempts to bind +// the params to it. +func (r *In) Params() (*Params, error) { + params := Params{} + + err := json.Unmarshal(r.RawParams, ¶ms) + if err != nil { + return nil, errors.Errorf("error parsing params field in payload: %s", err) + } + + return ¶ms, nil +} diff --git a/pkg/rpc/errors.go b/pkg/rpc/response/errors.go similarity index 67% rename from pkg/rpc/errors.go rename to pkg/rpc/response/errors.go index 966f5f678..a0d468136 100644 --- a/pkg/rpc/errors.go +++ b/pkg/rpc/response/errors.go @@ -1,4 +1,4 @@ -package rpc +package response import ( "fmt" @@ -18,10 +18,13 @@ type ( ) var ( - errInvalidParams = NewInvalidParamsError("", nil) + // ErrInvalidParams represents a generic 'invalid parameters' error. + ErrInvalidParams = NewInvalidParamsError("", nil) ) -func newError(code int64, httpCode int, message string, data string, cause error) *Error { +// NewError is an Error constructor that takes Error contents from its +// parameters. +func NewError(code int64, httpCode int, message string, data string, cause error) *Error { return &Error{ Code: code, HTTPCode: httpCode, @@ -35,40 +38,40 @@ func newError(code int64, httpCode int, message string, data string, cause error // NewParseError creates a new error with code // -32700.:%s func NewParseError(data string, cause error) *Error { - return newError(-32700, http.StatusBadRequest, "Parse Error", data, cause) + return NewError(-32700, http.StatusBadRequest, "Parse Error", data, cause) } // NewInvalidRequestError creates a new error with // code -32600. func NewInvalidRequestError(data string, cause error) *Error { - return newError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause) + return NewError(-32600, http.StatusUnprocessableEntity, "Invalid Request", data, cause) } // NewMethodNotFoundError creates a new error with // code -32601. func NewMethodNotFoundError(data string, cause error) *Error { - return newError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause) + return NewError(-32601, http.StatusMethodNotAllowed, "Method not found", data, cause) } // NewInvalidParamsError creates a new error with // code -32602. func NewInvalidParamsError(data string, cause error) *Error { - return newError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause) + return NewError(-32602, http.StatusUnprocessableEntity, "Invalid Params", data, cause) } // NewInternalServerError creates a new error with // code -32603. func NewInternalServerError(data string, cause error) *Error { - return newError(-32603, http.StatusInternalServerError, "Internal error", data, cause) + return NewError(-32603, http.StatusInternalServerError, "Internal error", data, cause) } // NewRPCError creates a new error with // code -100 func NewRPCError(message string, data string, cause error) *Error { - return newError(-100, http.StatusUnprocessableEntity, message, data, cause) + return NewError(-100, http.StatusUnprocessableEntity, message, data, cause) } // Error implements the error interface. -func (e Error) Error() string { +func (e *Error) Error() string { return fmt.Sprintf("%s (%d) - %s - %s", e.Message, e.Code, e.Data, e.Cause) } diff --git a/pkg/rpc/types.go b/pkg/rpc/response/types.go similarity index 52% rename from pkg/rpc/types.go rename to pkg/rpc/response/types.go index 3c02b2018..cd0985b54 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/response/types.go @@ -1,15 +1,17 @@ -package rpc +package response import ( + "encoding/json" + "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/vm" ) -// InvokeScriptResponse stores response for the invoke script call. -type InvokeScriptResponse struct { - responseHeader - Error *Error `json:"error,omitempty"` +// InvokeScript stores response for the invoke script call. +type InvokeScript struct { + HeaderAndError Result *InvokeResult `json:"result,omitempty"` } @@ -19,19 +21,18 @@ type InvokeResult struct { State vm.State `json:"state"` GasConsumed string `json:"gas_consumed"` Script string `json:"script"` - Stack []StackParam + Stack []request.StackParam } -// AccountStateResponse holds the getaccountstate response. -type AccountStateResponse struct { - responseHeader +// AccountState holds the getaccountstate response. +type AccountState struct { + Header Result *Account `json:"result"` } -// UnspentResponse represents server response to the `getunspents` command. -type UnspentResponse struct { - responseHeader - Error *Error `json:"error,omitempty"` +// Unspent represents server response to the `getunspents` command. +type Unspent struct { + HeaderAndError Result *result.Unspents `json:"result,omitempty"` } @@ -51,68 +52,54 @@ type Balance struct { Value string `json:"value"` } -type params struct { - values []interface{} +// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version). +type Header struct { + ID json.RawMessage `json:"id"` + JSONRPC string `json:"jsonrpc"` } -func newParams(vals ...interface{}) params { - p := params{} - p.values = make([]interface{}, len(vals)) - for i := 0; i < len(p.values); i++ { - p.values[i] = vals[i] - } - return p +// HeaderAndError adds an Error (that can be empty) to the Header, it's used +// to construct type-specific responses. +type HeaderAndError struct { + Header + Error *Error `json:"error,omitempty"` } -type request struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params []interface{} `json:"params"` - ID int `json:"id"` +// Raw represents a standard raw JSON-RPC 2.0 +// response: http://www.jsonrpc.org/specification#response_object. +type Raw struct { + HeaderAndError + Result interface{} `json:"result,omitempty"` } -type responseHeader struct { - ID int `json:"id"` - JSONRPC string `json:"jsonrpc"` +// SendToAddress stores response for the sendtoaddress call. +type SendToAddress struct { + HeaderAndError + Result *Tx } -type response struct { - responseHeader - Error *Error `json:"error"` - Result interface{} `json:"result"` -} - -// SendToAddressResponse stores response for the sendtoaddress call. -type SendToAddressResponse struct { - responseHeader - Error *Error `json:"error"` - Result *TxResponse -} - -// GetRawTxResponse represents verbose output of `getrawtransaction` RPC call. -type GetRawTxResponse struct { - responseHeader - Error *Error `json:"error"` - Result *RawTxResponse `json:"result"` -} - -// GetTxOutResponse represents result of `gettxout` RPC call. -type GetTxOutResponse struct { - responseHeader - Error *Error +// GetTxOut represents result of `gettxout` RPC call. +type GetTxOut struct { + HeaderAndError Result *result.TransactionOutput } -// RawTxResponse stores transaction with blockchain metadata to be sent as a response. -type RawTxResponse struct { - TxResponse +// GetRawTx represents verbose output of `getrawtransaction` RPC call. +type GetRawTx struct { + HeaderAndError + Result *RawTx `json:"result"` +} + +// RawTx stores transaction with blockchain metadata to be sent as a response. +type RawTx struct { + Tx BlockHash string `json:"blockhash"` Confirmations uint `json:"confirmations"` BlockTime uint `json:"blocktime"` } -// TxResponse stores transaction to be sent as a response. -type TxResponse struct { +// Tx stores transaction to be sent as a response. +type Tx struct { TxID string `json:"txid"` Size int `json:"size"` Type string `json:"type"` // todo: convert to TransactionType diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index 8ba1af6c5..29a9f1a15 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -7,6 +7,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" @@ -17,11 +19,11 @@ import ( // missing output wrapper at the moment, thus commented out // func (c *Client) getBlock(indexOrHash interface{}, verbose bool) (*response, error) { // var ( -// params = newParams(indexOrHash) +// params = request.NewRawParams(indexOrHash) // resp = &response{} // ) // if verbose { -// params = newParams(indexOrHash, 1) +// params = request.NewRawParams(indexOrHash, 1) // } // if err := c.performRequest("getblock", params, resp); err != nil { // return nil, err @@ -30,10 +32,10 @@ import ( // } // GetAccountState returns detailed information about a NEO account. -func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) { +func (c *Client) GetAccountState(address string) (*response.AccountState, error) { var ( - params = newParams(address) - resp = &AccountStateResponse{} + params = request.NewRawParams(address) + resp = &response.AccountState{} ) if err := c.performRequest("getaccountstate", params, resp); err != nil { return nil, err @@ -42,10 +44,10 @@ func (c *Client) GetAccountState(address string) (*AccountStateResponse, error) } // GetUnspents returns UTXOs for the given NEO account. -func (c *Client) GetUnspents(address string) (*UnspentResponse, error) { +func (c *Client) GetUnspents(address string) (*response.Unspent, error) { var ( - params = newParams(address) - resp = &UnspentResponse{} + params = request.NewRawParams(address) + resp = &response.Unspent{} ) if err := c.performRequest("getunspents", params, resp); err != nil { return nil, err @@ -55,10 +57,10 @@ func (c *Client) GetUnspents(address string) (*UnspentResponse, error) { // InvokeScript returns the result of the given script after running it true the VM. // NOTE: This is a test invoke and will not affect the blockchain. -func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) { +func (c *Client) InvokeScript(script string) (*response.InvokeScript, error) { var ( - params = newParams(script) - resp = &InvokeScriptResponse{} + params = request.NewRawParams(script) + resp = &response.InvokeScript{} ) if err := c.performRequest("invokescript", params, resp); err != nil { return nil, err @@ -69,10 +71,10 @@ func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) { // InvokeFunction returns the results after calling the smart contract scripthash // with the given operation and parameters. // NOTE: this is test invoke and will not affect the blockchain. -func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) { +func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*response.InvokeScript, error) { var ( - p = newParams(script, operation, params) - resp = &InvokeScriptResponse{} + p = request.NewRawParams(script, operation, params) + resp = &response.InvokeScript{} ) if err := c.performRequest("invokefunction", p, resp); err != nil { return nil, err @@ -82,10 +84,10 @@ func (c *Client) InvokeFunction(script, operation string, params []smartcontract // Invoke returns the results after calling the smart contract scripthash // with the given parameters. -func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) { +func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*response.InvokeScript, error) { var ( - p = newParams(script, params) - resp = &InvokeScriptResponse{} + p = request.NewRawParams(script, params) + resp = &response.InvokeScript{} ) if err := c.performRequest("invoke", p, resp); err != nil { return nil, err @@ -97,7 +99,7 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok // missing output wrapper at the moment, thus commented out // func (c *Client) getRawTransaction(hash string, verbose bool) (*response, error) { // var ( -// params = newParams(hash, verbose) +// params = request.NewRawParams(hash, verbose) // resp = &response{} // ) // if err := c.performRequest("getrawtransaction", params, resp); err != nil { @@ -110,10 +112,10 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*Invok // The given hex string needs to be signed with a keypair. // When the result of the response object is true, the TX has successfully // been broadcasted to the network. -func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, error) { +func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response.Raw, error) { var ( - params = newParams(hex.EncodeToString(rawTX.Bytes())) - resp = &response{} + params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes())) + resp = &response.Raw{} ) if err := c.performRequest("sendrawtransaction", params, resp); err != nil { return nil, err @@ -124,7 +126,7 @@ func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) (*response, // SendToAddress sends an amount of specific asset to a given address. // This call requires open wallet. (`wif` key in client struct.) // If response.Result is `true` then transaction was formed correctly and was written in blockchain. -func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*SendToAddressResponse, error) { +func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.Fixed8) (*response.SendToAddress, error) { var ( err error rawTx *transaction.Transaction @@ -135,23 +137,23 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F wif: c.WIF(), balancer: c.Balancer(), } - resp *response - response = &SendToAddressResponse{} + respRaw *response.Raw + resp = &response.SendToAddress{} ) if rawTx, err = CreateRawContractTransaction(txParams); err != nil { return nil, errors.Wrap(err, "failed to create raw transaction for `sendtoaddress`") } - if resp, err = c.sendRawTransaction(rawTx); err != nil { + if respRaw, err = c.sendRawTransaction(rawTx); err != nil { return nil, errors.Wrap(err, "failed to send raw transaction") } - response.Error = resp.Error - response.ID = resp.ID - response.JSONRPC = resp.JSONRPC - response.Result = &TxResponse{ + resp.Error = respRaw.Error + resp.ID = respRaw.ID + resp.JSONRPC = respRaw.JSONRPC + resp.Result = &response.Tx{ TxID: rawTx.Hash().StringLE(), } - return response, nil + return resp, nil } // SignAndPushInvocationTx signs and pushes given script as an invocation diff --git a/pkg/rpc/scdetails.go b/pkg/rpc/scdetails.go index 5988e3436..f5cd242c8 100644 --- a/pkg/rpc/scdetails.go +++ b/pkg/rpc/scdetails.go @@ -1,5 +1,7 @@ package rpc +import "github.com/CityOfZion/neo-go/pkg/rpc/request" + // ContractDetails contains contract metadata. type ContractDetails struct { Author string @@ -10,6 +12,6 @@ type ContractDetails struct { HasStorage bool HasDynamicInvocation bool IsPayable bool - ReturnType StackParamType - Parameters []StackParamType + ReturnType request.StackParamType + Parameters []request.StackParamType } diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index f0080fa27..8b3553366 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -3,6 +3,7 @@ package rpc import ( "context" "encoding/hex" + "encoding/json" "fmt" "net/http" "strconv" @@ -14,6 +15,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" + "github.com/CityOfZion/neo-go/pkg/rpc/request" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/pkg/errors" @@ -71,13 +74,13 @@ func (s *Server) Shutdown() error { } func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request) { - req := NewRequest() + req := request.NewIn() if httpRequest.Method != "POST" { s.WriteErrorResponse( req, w, - NewInvalidParamsError( + response.NewInvalidParamsError( fmt.Sprintf("Invalid method '%s', please retry with 'POST'", httpRequest.Method), nil, ), ) @@ -86,20 +89,20 @@ func (s *Server) requestHandler(w http.ResponseWriter, httpRequest *http.Request err := req.DecodeData(httpRequest.Body) if err != nil { - s.WriteErrorResponse(req, w, NewParseError("Problem parsing JSON-RPC request body", err)) + s.WriteErrorResponse(req, w, response.NewParseError("Problem parsing JSON-RPC request body", err)) return } reqParams, err := req.Params() if err != nil { - s.WriteErrorResponse(req, w, NewInvalidParamsError("Problem parsing request parameters", err)) + s.WriteErrorResponse(req, w, response.NewInvalidParamsError("Problem parsing request parameters", err)) return } s.methodHandler(w, req, *reqParams) } -func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) { +func (s *Server) methodHandler(w http.ResponseWriter, req *request.In, reqParams request.Params) { s.log.Debug("processing rpc request", zap.String("method", req.Method), zap.String("params", fmt.Sprintf("%v", reqParams))) @@ -121,33 +124,33 @@ Methods: param, ok := reqParams.Value(0) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } switch param.Type { - case stringT: + case request.StringT: var err error hash, err = param.GetUint256() if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } - case numberT: + case request.NumberT: num, err := s.blockHeightFromParam(param) if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } hash = s.chain.GetHeaderHash(num) default: - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } block, err := s.chain.GetBlock(hash) if err != nil { - resultsErr = NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) + resultsErr = response.NewInternalServerError(fmt.Sprintf("Problem locating block with hash: %s", hash), err) break } @@ -165,14 +168,14 @@ Methods: case "getblockhash": getblockHashCalled.Inc() - param, ok := reqParams.ValueWithType(0, numberT) + param, ok := reqParams.ValueWithType(0, request.NumberT) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } num, err := s.blockHeightFromParam(param) if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } @@ -206,22 +209,22 @@ Methods: validateaddressCalled.Inc() param, ok := reqParams.Value(0) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } results = validateAddress(param.Value) case "getassetstate": getassetstateCalled.Inc() - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break Methods } paramAssetID, err := param.GetUint256() if err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams break } @@ -229,7 +232,7 @@ Methods: if as != nil { results = result.NewAssetState(as) } else { - resultsErr = NewRPCError("Unknown asset", "", nil) + resultsErr = response.NewRPCError("Unknown asset", "", nil) } case "getaccountstate": @@ -266,7 +269,7 @@ Methods: results, resultsErr = s.sendrawtransaction(reqParams) default: - resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) + resultsErr = response.NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil) } if resultsErr != nil { @@ -277,27 +280,27 @@ Methods: s.WriteResponse(req, w, results) } -func (s *Server) getStorage(ps Params) (interface{}, error) { +func (s *Server) getStorage(ps request.Params) (interface{}, error) { param, ok := ps.Value(0) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := param.GetUint160FromHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash = scriptHash.Reverse() param, ok = ps.Value(1) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } key, err := param.GetBytesHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } item := s.chain.GetStorageItem(scriptHash.Reverse(), key) @@ -308,22 +311,22 @@ func (s *Server) getStorage(ps Params) (interface{}, error) { return hex.EncodeToString(item.Value), nil } -func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { +func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, error) { var resultsErr error var results interface{} if param0, ok := reqParams.Value(0); !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if txHash, err := param0.GetUint256(); err != nil { - resultsErr = errInvalidParams + resultsErr = response.ErrInvalidParams } else if tx, height, err := s.chain.GetTransaction(txHash); err != nil { err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash) - return nil, NewRPCError("Unknown transaction", err.Error(), err) + return nil, response.NewRPCError("Unknown transaction", err.Error(), err) } else if len(reqParams) >= 2 { _header := s.chain.GetHeaderHash(int(height)) header, err := s.chain.GetHeader(_header) if err != nil { - resultsErr = NewInvalidParamsError(err.Error(), err) + resultsErr = response.NewInvalidParamsError(err.Error(), err) } param1, _ := reqParams.Value(1) @@ -345,34 +348,34 @@ func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) { return results, resultsErr } -func (s *Server) getTxOut(ps Params) (interface{}, error) { +func (s *Server) getTxOut(ps request.Params) (interface{}, error) { p, ok := ps.Value(0) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } h, err := p.GetUint256() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } - p, ok = ps.ValueWithType(1, numberT) + p, ok = ps.ValueWithType(1, request.NumberT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } num, err := p.GetInt() if err != nil || num < 0 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } tx, _, err := s.chain.GetTransaction(h) if err != nil { - return nil, NewInvalidParamsError(err.Error(), err) + return nil, response.NewInvalidParamsError(err.Error(), err) } if num >= len(tx.Outputs) { - return nil, NewInvalidParamsError("invalid index", errors.New("too big index")) + return nil, response.NewInvalidParamsError("invalid index", errors.New("too big index")) } out := tx.Outputs[num] @@ -380,35 +383,35 @@ func (s *Server) getTxOut(ps Params) (interface{}, error) { } // getContractState returns contract state (contract information, according to the contract script hash). -func (s *Server) getContractState(reqParams Params) (interface{}, error) { +func (s *Server) getContractState(reqParams request.Params) (interface{}, error) { var results interface{} - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if scriptHash, err := param.GetUint160FromHex(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { cs := s.chain.GetContractState(scriptHash) if cs != nil { results = result.NewContractState(cs) } else { - return nil, NewRPCError("Unknown contract", "", nil) + return nil, response.NewRPCError("Unknown contract", "", nil) } } return results, nil } // getAccountState returns account state either in short or full (unspents included) form. -func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, error) { +func (s *Server) getAccountState(reqParams request.Params, unspents bool) (interface{}, error) { var resultsErr error var results interface{} - param, ok := reqParams.ValueWithType(0, stringT) + param, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if scriptHash, err := param.GetUint160FromAddress(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { as := s.chain.GetAccountState(scriptHash) if as == nil { @@ -417,7 +420,7 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, if unspents { str, err := param.GetString() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } results = result.NewUnspents(as, s.chain, str) } else { @@ -428,18 +431,18 @@ func (s *Server) getAccountState(reqParams Params, unspents bool) (interface{}, } // invoke implements the `invoke` RPC call. -func (s *Server) invoke(reqParams Params) (interface{}, error) { - scriptHashHex, ok := reqParams.ValueWithType(0, stringT) +func (s *Server) invoke(reqParams request.Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := scriptHashHex.GetUint160FromHex() if err != nil { return nil, err } - sliceP, ok := reqParams.ValueWithType(1, arrayT) + sliceP, ok := reqParams.ValueWithType(1, request.ArrayT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } slice, err := sliceP.GetArray() if err != nil { @@ -453,10 +456,10 @@ func (s *Server) invoke(reqParams Params) (interface{}, error) { } // invokescript implements the `invokescript` RPC call. -func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { - scriptHashHex, ok := reqParams.ValueWithType(0, stringT) +func (s *Server) invokeFunction(reqParams request.Params) (interface{}, error) { + scriptHashHex, ok := reqParams.ValueWithType(0, request.StringT) if !ok { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } scriptHash, err := scriptHashHex.GetUint160FromHex() if err != nil { @@ -470,14 +473,14 @@ func (s *Server) invokeFunction(reqParams Params) (interface{}, error) { } // invokescript implements the `invokescript` RPC call. -func (s *Server) invokescript(reqParams Params) (interface{}, error) { +func (s *Server) invokescript(reqParams request.Params) (interface{}, error) { if len(reqParams) < 1 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } script, err := reqParams[0].GetBytesHex() if err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } return s.runScriptInVM(script), nil @@ -499,14 +502,14 @@ func (s *Server) runScriptInVM(script []byte) *result.Invoke { return result } -func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { +func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) { var resultsErr error var results interface{} if len(reqParams) < 1 { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else if byteTx, err := reqParams[0].GetBytesHex(); err != nil { - return nil, errInvalidParams + return nil, response.ErrInvalidParams } else { r := io.NewBinReaderFromBuf(byteTx) tx := &transaction.Transaction{} @@ -533,14 +536,14 @@ func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) { } } if err != nil { - resultsErr = NewInternalServerError(err.Error(), err) + resultsErr = response.NewInternalServerError(err.Error(), err) } } return results, resultsErr } -func (s *Server) blockHeightFromParam(param *Param) (int, error) { +func (s *Server) blockHeightFromParam(param *request.Param) (int, error) { num, err := param.GetInt() if err != nil { return 0, nil @@ -552,6 +555,71 @@ func (s *Server) blockHeightFromParam(param *Param) (int, error) { return num, nil } +// WriteErrorResponse writes an error response to the ResponseWriter. +func (s *Server) WriteErrorResponse(r *request.In, w http.ResponseWriter, err error) { + jsonErr, ok := err.(*response.Error) + if !ok { + jsonErr = response.NewInternalServerError("Internal server error", err) + } + + resp := response.Raw{ + HeaderAndError: response.HeaderAndError{ + Header: response.Header{ + JSONRPC: r.JSONRPC, + ID: r.RawID, + }, + Error: jsonErr, + }, + } + + logFields := []zap.Field{ + zap.Error(jsonErr.Cause), + zap.String("method", r.Method), + } + + params, err := r.Params() + if err == nil { + logFields = append(logFields, zap.Any("params", params)) + } + + s.log.Error("Error encountered with rpc request", logFields...) + + w.WriteHeader(jsonErr.HTTPCode) + s.writeServerResponse(r, w, resp) +} + +// WriteResponse encodes the response and writes it to the ResponseWriter. +func (s *Server) WriteResponse(r *request.In, w http.ResponseWriter, result interface{}) { + resp := response.Raw{ + HeaderAndError: response.HeaderAndError{ + Header: response.Header{ + JSONRPC: r.JSONRPC, + ID: r.RawID, + }, + }, + Result: result, + } + + s.writeServerResponse(r, w, resp) +} + +func (s *Server) writeServerResponse(r *request.In, w http.ResponseWriter, resp response.Raw) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if s.config.EnableCORSWorkaround { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") + } + + encoder := json.NewEncoder(w) + err := encoder.Encode(resp) + + if err != nil { + s.log.Error("Error encountered while encoding response", + zap.String("err", err.Error()), + zap.String("method", r.Method)) + } +} + // validateAddress verifies that the address is a correct NEO address // see https://docs.neo.org/en-us/node/cli/2.9.4/api/validateaddress.html func validateAddress(addr interface{}) result.ValidateAddress { diff --git a/pkg/rpc/server_helper_test.go b/pkg/rpc/server_helper_test.go index 1e1ae64f6..8b00d684e 100644 --- a/pkg/rpc/server_helper_test.go +++ b/pkg/rpc/server_helper_test.go @@ -11,6 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/network" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/require" @@ -38,11 +39,11 @@ type SendTXResponse struct { type InvokeFunctionResponse struct { Jsonrpc string `json:"jsonrpc"` Result struct { - Script string `json:"script"` - State string `json:"state"` - GasConsumed string `json:"gas_consumed"` - Stack []FuncParam `json:"stack"` - TX string `json:"tx,omitempty"` + Script string `json:"script"` + State string `json:"state"` + GasConsumed string `json:"gas_consumed"` + Stack []request.FuncParam `json:"stack"` + TX string `json:"tx,omitempty"` } `json:"result"` ID int `json:"id"` } diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 6d47f08dd..cad05c0fb 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -14,6 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/rpc/response" "github.com/CityOfZion/neo-go/pkg/rpc/response/result" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" @@ -602,7 +603,7 @@ func TestRPC(t *testing.T) { body := doRPCCall(rpc, handler, t) checkErrResponse(t, body, false) - var result GetTxOutResponse + var result response.GetTxOut err := json.Unmarshal(body, &result) require.NoErrorf(t, err, "could not parse response: %s", body) assert.Equal(t, 0, result.Result.N) diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/txBuilder.go index 349343bc6..a641592a3 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/txBuilder.go @@ -9,6 +9,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm/emit" @@ -141,38 +142,38 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro // expandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. -func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { +func expandArrayIntoScript(script *io.BinWriter, slice []request.Param) error { for j := len(slice) - 1; j >= 0; j-- { fp, err := slice[j].GetFuncParam() if err != nil { return err } switch fp.Type { - case ByteArray, Signature: + case request.ByteArray, request.Signature: str, err := fp.Value.GetBytesHex() if err != nil { return err } emit.Bytes(script, str) - case String: + case request.String: str, err := fp.Value.GetString() if err != nil { return err } emit.String(script, str) - case Hash160: + case request.Hash160: hash, err := fp.Value.GetUint160FromHex() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case Hash256: + case request.Hash256: hash, err := fp.Value.GetUint256() if err != nil { return err } emit.Bytes(script, hash.BytesBE()) - case PublicKey: + case request.PublicKey: str, err := fp.Value.GetString() if err != nil { return err @@ -182,13 +183,13 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { return err } emit.Bytes(script, key.Bytes()) - case Integer: + case request.Integer: val, err := fp.Value.GetInt() if err != nil { return err } emit.Int(script, int64(val)) - case Boolean: + case request.Boolean: str, err := fp.Value.GetString() if err != nil { return err @@ -210,19 +211,19 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { // CreateFunctionInvocationScript creates a script to invoke given contract with // given parameters. -func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) { +func CreateFunctionInvocationScript(contract util.Uint160, params request.Params) ([]byte, error) { script := io.NewBufBinWriter() for i := len(params) - 1; i >= 0; i-- { switch params[i].Type { - case stringT: + case request.StringT: emit.String(script.BinWriter, params[i].String()) - case numberT: + case request.NumberT: num, err := params[i].GetInt() if err != nil { return nil, err } emit.String(script.BinWriter, strconv.Itoa(num)) - case arrayT: + case request.ArrayT: slice, err := params[i].GetArray() if err != nil { return nil, err @@ -244,7 +245,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt // given parameters. It differs from CreateFunctionInvocationScript in that it // expects one array of FuncParams and expands it onto the stack as independent // elements. -func CreateInvocationScript(contract util.Uint160, funcParams []Param) ([]byte, error) { +func CreateInvocationScript(contract util.Uint160, funcParams []request.Param) ([]byte, error) { script := io.NewBufBinWriter() err := expandArrayIntoScript(script.BinWriter, funcParams) if err != nil { diff --git a/pkg/rpc/tx_builder_test.go b/pkg/rpc/tx_builder_test.go index 2a705ae8e..453a503b3 100644 --- a/pkg/rpc/tx_builder_test.go +++ b/pkg/rpc/tx_builder_test.go @@ -4,56 +4,57 @@ import ( "encoding/hex" "testing" + "github.com/CityOfZion/neo-go/pkg/rpc/request" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestInvocationScriptCreationGood(t *testing.T) { - p := Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"} + p := request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"} contract, err := p.GetUint160FromHex() require.Nil(t, err) var paramScripts = []struct { - ps Params + ps request.Params script string }{{ script: "676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "transfer"}}, + ps: request.Params{{Type: request.StringT, Value: "transfer"}}, script: "087472616e73666572676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{numberT, 42}}, + ps: request.Params{{Type: request.NumberT, Value: 42}}, script: "023432676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{}}}, script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.ByteArray, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Signature, Value: request.Param{Type: request.StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}}, script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{String, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.String, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash160, Value: request.Param{Type: request.StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}}, script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash256, Value: request.Param{Type: request.StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}}, script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}}, script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Integer, Param{numberT, 42}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Integer, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "true"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "true"}}}}}}, script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }, { - ps: Params{{stringT, "a"}, {arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "false"}}}}}}, + ps: request.Params{{Type: request.StringT, Value: "a"}, {Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "false"}}}}}}, script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50", }} for _, ps := range paramScripts { @@ -66,21 +67,21 @@ func TestInvocationScriptCreationGood(t *testing.T) { func TestInvocationScriptCreationBad(t *testing.T) { contract := util.Uint160{} - var testParams = []Params{ - {{numberT, "qwerty"}}, - {{arrayT, 42}}, - {{arrayT, []Param{{numberT, 42}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{ByteArray, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Signature, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{String, Param{numberT, 42}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Hash160, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Hash256, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{numberT, 42}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{PublicKey, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Integer, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{numberT, 42}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Boolean, Param{stringT, "qwerty"}}}}}}, - {{arrayT, []Param{{funcParamT, FuncParam{Unknown, Param{}}}}}}, + var testParams = []request.Params{ + {{Type: request.NumberT, Value: "qwerty"}}, + {{Type: request.ArrayT, Value: 42}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.NumberT, Value: 42}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.ByteArray, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Signature, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.String, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash160, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Hash256, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.PublicKey, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Integer, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.NumberT, Value: 42}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Boolean, Value: request.Param{Type: request.StringT, Value: "qwerty"}}}}}}, + {{Type: request.ArrayT, Value: []request.Param{{Type: request.FuncParamT, Value: request.FuncParam{Type: request.Unknown, Value: request.Param{}}}}}}, } for _, ps := range testParams { _, err := CreateFunctionInvocationScript(contract, ps)