github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/lib/types/types.go (about)

     1  package rpctypes
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	amino "github.com/evdatsion/go-amino"
    14  )
    15  
    16  // a wrapper to emulate a sum type: jsonrpcid = string | int
    17  // TODO: refactor when Go 2.0 arrives https://github.com/golang/go/issues/19412
    18  type jsonrpcid interface {
    19  	isJSONRPCID()
    20  }
    21  
    22  // JSONRPCStringID a wrapper for JSON-RPC string IDs
    23  type JSONRPCStringID string
    24  
    25  func (JSONRPCStringID) isJSONRPCID() {}
    26  
    27  // JSONRPCIntID a wrapper for JSON-RPC integer IDs
    28  type JSONRPCIntID int
    29  
    30  func (JSONRPCIntID) isJSONRPCID() {}
    31  
    32  func idFromInterface(idInterface interface{}) (jsonrpcid, error) {
    33  	switch id := idInterface.(type) {
    34  	case string:
    35  		return JSONRPCStringID(id), nil
    36  	case float64:
    37  		// json.Unmarshal uses float64 for all numbers
    38  		// (https://golang.org/pkg/encoding/json/#Unmarshal),
    39  		// but the JSONRPC2.0 spec says the id SHOULD NOT contain
    40  		// decimals - so we truncate the decimals here.
    41  		return JSONRPCIntID(int(id)), nil
    42  	default:
    43  		typ := reflect.TypeOf(id)
    44  		return nil, fmt.Errorf("JSON-RPC ID (%v) is of unknown type (%v)", id, typ)
    45  	}
    46  }
    47  
    48  //----------------------------------------
    49  // REQUEST
    50  
    51  type RPCRequest struct {
    52  	JSONRPC string          `json:"jsonrpc"`
    53  	ID      jsonrpcid       `json:"id"`
    54  	Method  string          `json:"method"`
    55  	Params  json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
    56  }
    57  
    58  // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int
    59  func (request *RPCRequest) UnmarshalJSON(data []byte) error {
    60  	unsafeReq := &struct {
    61  		JSONRPC string          `json:"jsonrpc"`
    62  		ID      interface{}     `json:"id"`
    63  		Method  string          `json:"method"`
    64  		Params  json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
    65  	}{}
    66  	err := json.Unmarshal(data, &unsafeReq)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	request.JSONRPC = unsafeReq.JSONRPC
    71  	request.Method = unsafeReq.Method
    72  	request.Params = unsafeReq.Params
    73  	if unsafeReq.ID == nil {
    74  		return nil
    75  	}
    76  	id, err := idFromInterface(unsafeReq.ID)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	request.ID = id
    81  	return nil
    82  }
    83  
    84  func NewRPCRequest(id jsonrpcid, method string, params json.RawMessage) RPCRequest {
    85  	return RPCRequest{
    86  		JSONRPC: "2.0",
    87  		ID:      id,
    88  		Method:  method,
    89  		Params:  params,
    90  	}
    91  }
    92  
    93  func (req RPCRequest) String() string {
    94  	return fmt.Sprintf("[%s %s]", req.ID, req.Method)
    95  }
    96  
    97  func MapToRequest(cdc *amino.Codec, id jsonrpcid, method string, params map[string]interface{}) (RPCRequest, error) {
    98  	var params_ = make(map[string]json.RawMessage, len(params))
    99  	for name, value := range params {
   100  		valueJSON, err := cdc.MarshalJSON(value)
   101  		if err != nil {
   102  			return RPCRequest{}, err
   103  		}
   104  		params_[name] = valueJSON
   105  	}
   106  	payload, err := json.Marshal(params_) // NOTE: Amino doesn't handle maps yet.
   107  	if err != nil {
   108  		return RPCRequest{}, err
   109  	}
   110  	request := NewRPCRequest(id, method, payload)
   111  	return request, nil
   112  }
   113  
   114  func ArrayToRequest(cdc *amino.Codec, id jsonrpcid, method string, params []interface{}) (RPCRequest, error) {
   115  	var params_ = make([]json.RawMessage, len(params))
   116  	for i, value := range params {
   117  		valueJSON, err := cdc.MarshalJSON(value)
   118  		if err != nil {
   119  			return RPCRequest{}, err
   120  		}
   121  		params_[i] = valueJSON
   122  	}
   123  	payload, err := json.Marshal(params_) // NOTE: Amino doesn't handle maps yet.
   124  	if err != nil {
   125  		return RPCRequest{}, err
   126  	}
   127  	request := NewRPCRequest(id, method, payload)
   128  	return request, nil
   129  }
   130  
   131  //----------------------------------------
   132  // RESPONSE
   133  
   134  type RPCError struct {
   135  	Code    int    `json:"code"`
   136  	Message string `json:"message"`
   137  	Data    string `json:"data,omitempty"`
   138  }
   139  
   140  func (err RPCError) Error() string {
   141  	const baseFormat = "RPC error %v - %s"
   142  	if err.Data != "" {
   143  		return fmt.Sprintf(baseFormat+": %s", err.Code, err.Message, err.Data)
   144  	}
   145  	return fmt.Sprintf(baseFormat, err.Code, err.Message)
   146  }
   147  
   148  type RPCResponse struct {
   149  	JSONRPC string          `json:"jsonrpc"`
   150  	ID      jsonrpcid       `json:"id"`
   151  	Result  json.RawMessage `json:"result,omitempty"`
   152  	Error   *RPCError       `json:"error,omitempty"`
   153  }
   154  
   155  // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int
   156  func (response *RPCResponse) UnmarshalJSON(data []byte) error {
   157  	unsafeResp := &struct {
   158  		JSONRPC string          `json:"jsonrpc"`
   159  		ID      interface{}     `json:"id"`
   160  		Result  json.RawMessage `json:"result,omitempty"`
   161  		Error   *RPCError       `json:"error,omitempty"`
   162  	}{}
   163  	err := json.Unmarshal(data, &unsafeResp)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	response.JSONRPC = unsafeResp.JSONRPC
   168  	response.Error = unsafeResp.Error
   169  	response.Result = unsafeResp.Result
   170  	if unsafeResp.ID == nil {
   171  		return nil
   172  	}
   173  	id, err := idFromInterface(unsafeResp.ID)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	response.ID = id
   178  	return nil
   179  }
   180  
   181  func NewRPCSuccessResponse(cdc *amino.Codec, id jsonrpcid, res interface{}) RPCResponse {
   182  	var rawMsg json.RawMessage
   183  
   184  	if res != nil {
   185  		var js []byte
   186  		js, err := cdc.MarshalJSON(res)
   187  		if err != nil {
   188  			return RPCInternalError(id, errors.Wrap(err, "Error marshalling response"))
   189  		}
   190  		rawMsg = json.RawMessage(js)
   191  	}
   192  
   193  	return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg}
   194  }
   195  
   196  func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse {
   197  	return RPCResponse{
   198  		JSONRPC: "2.0",
   199  		ID:      id,
   200  		Error:   &RPCError{Code: code, Message: msg, Data: data},
   201  	}
   202  }
   203  
   204  func (resp RPCResponse) String() string {
   205  	if resp.Error == nil {
   206  		return fmt.Sprintf("[%s %v]", resp.ID, resp.Result)
   207  	}
   208  	return fmt.Sprintf("[%s %s]", resp.ID, resp.Error)
   209  }
   210  
   211  func RPCParseError(id jsonrpcid, err error) RPCResponse {
   212  	return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error())
   213  }
   214  
   215  func RPCInvalidRequestError(id jsonrpcid, err error) RPCResponse {
   216  	return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error())
   217  }
   218  
   219  func RPCMethodNotFoundError(id jsonrpcid) RPCResponse {
   220  	return NewRPCErrorResponse(id, -32601, "Method not found", "")
   221  }
   222  
   223  func RPCInvalidParamsError(id jsonrpcid, err error) RPCResponse {
   224  	return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error())
   225  }
   226  
   227  func RPCInternalError(id jsonrpcid, err error) RPCResponse {
   228  	return NewRPCErrorResponse(id, -32603, "Internal error", err.Error())
   229  }
   230  
   231  func RPCServerError(id jsonrpcid, err error) RPCResponse {
   232  	return NewRPCErrorResponse(id, -32000, "Server error", err.Error())
   233  }
   234  
   235  //----------------------------------------
   236  
   237  // WSRPCConnection represents a websocket connection.
   238  type WSRPCConnection interface {
   239  	// GetRemoteAddr returns a remote address of the connection.
   240  	GetRemoteAddr() string
   241  	// WriteRPCResponse writes the resp onto connection (BLOCKING).
   242  	WriteRPCResponse(resp RPCResponse)
   243  	// TryWriteRPCResponse tries to write the resp onto connection (NON-BLOCKING).
   244  	TryWriteRPCResponse(resp RPCResponse) bool
   245  	// Codec returns an Amino codec used.
   246  	Codec() *amino.Codec
   247  	// Context returns the connection's context.
   248  	Context() context.Context
   249  }
   250  
   251  // Context is the first parameter for all functions. It carries a json-rpc
   252  // request, http request and websocket connection.
   253  //
   254  // - JSONReq is non-nil when JSONRPC is called over websocket or HTTP.
   255  // - WSConn is non-nil when we're connected via a websocket.
   256  // - HTTPReq is non-nil when URI or JSONRPC is called over HTTP.
   257  type Context struct {
   258  	// json-rpc request
   259  	JSONReq *RPCRequest
   260  	// websocket connection
   261  	WSConn WSRPCConnection
   262  	// http request
   263  	HTTPReq *http.Request
   264  }
   265  
   266  // RemoteAddr returns the remote address (usually a string "IP:port").
   267  // If neither HTTPReq nor WSConn is set, an empty string is returned.
   268  // HTTP:
   269  //		http.Request#RemoteAddr
   270  // WS:
   271  //		result of GetRemoteAddr
   272  func (ctx *Context) RemoteAddr() string {
   273  	if ctx.HTTPReq != nil {
   274  		return ctx.HTTPReq.RemoteAddr
   275  	} else if ctx.WSConn != nil {
   276  		return ctx.WSConn.GetRemoteAddr()
   277  	}
   278  	return ""
   279  }
   280  
   281  // Context returns the request's context.
   282  // The returned context is always non-nil; it defaults to the background context.
   283  // HTTP:
   284  //		The context is canceled when the client's connection closes, the request
   285  //		is canceled (with HTTP/2), or when the ServeHTTP method returns.
   286  // WS:
   287  //		The context is canceled when the client's connections closes.
   288  func (ctx *Context) Context() context.Context {
   289  	if ctx.HTTPReq != nil {
   290  		return ctx.HTTPReq.Context()
   291  	} else if ctx.WSConn != nil {
   292  		return ctx.WSConn.Context()
   293  	}
   294  	return context.Background()
   295  }
   296  
   297  //----------------------------------------
   298  // SOCKETS
   299  
   300  //
   301  // Determine if its a unix or tcp socket.
   302  // If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port
   303  // TODO: deprecate
   304  func SocketType(listenAddr string) string {
   305  	socketType := "unix"
   306  	if len(strings.Split(listenAddr, ":")) >= 2 {
   307  		socketType = "tcp"
   308  	}
   309  	return socketType
   310  }