github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/rpc/jsonrpc/types/types.go (about)

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