github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/types/types.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	tmjson "github.com/ari-anchor/sei-tendermint/libs/json"
    15  	"github.com/ari-anchor/sei-tendermint/rpc/coretypes"
    16  )
    17  
    18  // ErrorCode is the type of JSON-RPC error codes.
    19  type ErrorCode int
    20  
    21  func (e ErrorCode) String() string {
    22  	if s, ok := errorCodeString[e]; ok {
    23  		return s
    24  	}
    25  	return fmt.Sprintf("server error: code %d", e)
    26  }
    27  
    28  // Constants defining the standard JSON-RPC error codes.
    29  const (
    30  	CodeParseError     ErrorCode = -32700 // Invalid JSON received by the server
    31  	CodeInvalidRequest ErrorCode = -32600 // The JSON sent is not a valid request object
    32  	CodeMethodNotFound ErrorCode = -32601 // The method does not exist or is unavailable
    33  	CodeInvalidParams  ErrorCode = -32602 // Invalid method parameters
    34  	CodeInternalError  ErrorCode = -32603 // Internal JSON-RPC error
    35  )
    36  
    37  var errorCodeString = map[ErrorCode]string{
    38  	CodeParseError:     "Parse error",
    39  	CodeInvalidRequest: "Invalid request",
    40  	CodeMethodNotFound: "Method not found",
    41  	CodeInvalidParams:  "Invalid params",
    42  	CodeInternalError:  "Internal error",
    43  }
    44  
    45  //----------------------------------------
    46  // REQUEST
    47  
    48  type RPCRequest struct {
    49  	id json.RawMessage
    50  
    51  	Method string
    52  	Params json.RawMessage
    53  }
    54  
    55  // NewRequest returns an empty request with the specified ID.
    56  func NewRequest(id int) RPCRequest {
    57  	return RPCRequest{id: []byte(strconv.Itoa(id))}
    58  }
    59  
    60  // ID returns a string representation of the request ID.
    61  func (req RPCRequest) ID() string { return string(req.id) }
    62  
    63  // IsNotification reports whether req is a notification (has an empty ID).
    64  func (req RPCRequest) IsNotification() bool { return len(req.id) == 0 }
    65  
    66  type rpcRequestJSON struct {
    67  	V  string          `json:"jsonrpc"` // must be "2.0"
    68  	ID json.RawMessage `json:"id,omitempty"`
    69  	M  string          `json:"method"`
    70  	P  json.RawMessage `json:"params"`
    71  }
    72  
    73  // isNullOrEmpty reports whether data is empty or the JSON "null" value.
    74  func isNullOrEmpty(data json.RawMessage) bool {
    75  	return len(data) == 0 || bytes.Equal(data, []byte("null"))
    76  }
    77  
    78  // validID matches the text of a JSON value that is allowed to serve as a
    79  // JSON-RPC request ID. Precondition: Target value is legal JSON.
    80  var validID = regexp.MustCompile(`^(?:".*"|-?\d+)$`)
    81  
    82  // UnmarshalJSON decodes a request from a JSON-RPC 2.0 request object.
    83  func (req *RPCRequest) UnmarshalJSON(data []byte) error {
    84  	var wrapper rpcRequestJSON
    85  	if err := json.Unmarshal(data, &wrapper); err != nil {
    86  		return err
    87  	} else if wrapper.V != "" && wrapper.V != "2.0" {
    88  		return fmt.Errorf("invalid version: %q", wrapper.V)
    89  	}
    90  
    91  	if !isNullOrEmpty(wrapper.ID) {
    92  		if !validID.Match(wrapper.ID) {
    93  			return fmt.Errorf("invalid request ID: %q", string(wrapper.ID))
    94  		}
    95  		req.id = wrapper.ID
    96  	}
    97  	req.Method = wrapper.M
    98  	req.Params = wrapper.P
    99  	return nil
   100  }
   101  
   102  // MarshalJSON marshals a request with the appropriate version tag.
   103  func (req RPCRequest) MarshalJSON() ([]byte, error) {
   104  	return json.Marshal(rpcRequestJSON{
   105  		V:  "2.0",
   106  		ID: req.id,
   107  		M:  req.Method,
   108  		P:  req.Params,
   109  	})
   110  }
   111  
   112  func (req RPCRequest) String() string {
   113  	return fmt.Sprintf("RPCRequest{%s %s/%X}", req.ID(), req.Method, req.Params)
   114  }
   115  
   116  // MakeResponse constructs a success response to req with the given result.  If
   117  // there is an error marshaling result to JSON, it returns an error response.
   118  func (req RPCRequest) MakeResponse(result interface{}) RPCResponse {
   119  	data, err := tmjson.Marshal(result)
   120  	if err != nil {
   121  		return req.MakeErrorf(CodeInternalError, "marshaling result: %v", err)
   122  	}
   123  	return RPCResponse{id: req.id, Result: data}
   124  }
   125  
   126  // MakeErrorf constructs an error response to req with the given code and a
   127  // message constructed by formatting msg with args.
   128  func (req RPCRequest) MakeErrorf(code ErrorCode, msg string, args ...interface{}) RPCResponse {
   129  	return RPCResponse{
   130  		id: req.id,
   131  		Error: &RPCError{
   132  			Code:    int(code),
   133  			Message: code.String(),
   134  			Data:    fmt.Sprintf(msg, args...),
   135  		},
   136  	}
   137  }
   138  
   139  // MakeError constructs an error response to req from the given error value.
   140  // This function will panic if err == nil.
   141  func (req RPCRequest) MakeError(err error) RPCResponse {
   142  	if err == nil {
   143  		panic("cannot construct an error response for nil")
   144  	}
   145  	if e, ok := err.(*RPCError); ok {
   146  		return RPCResponse{id: req.id, Error: e}
   147  	}
   148  	if errors.Is(err, coretypes.ErrZeroOrNegativeHeight) ||
   149  		errors.Is(err, coretypes.ErrZeroOrNegativePerPage) ||
   150  		errors.Is(err, coretypes.ErrPageOutOfRange) ||
   151  		errors.Is(err, coretypes.ErrInvalidRequest) {
   152  		return RPCResponse{id: req.id, Error: &RPCError{
   153  			Code:    int(CodeInvalidRequest),
   154  			Message: CodeInvalidRequest.String(),
   155  			Data:    err.Error(),
   156  		}}
   157  	}
   158  	return RPCResponse{id: req.id, Error: &RPCError{
   159  		Code:    int(CodeInternalError),
   160  		Message: CodeInternalError.String(),
   161  		Data:    err.Error(),
   162  	}}
   163  }
   164  
   165  // SetMethodAndParams updates the method and parameters of req with the given
   166  // values, leaving the ID unchanged.
   167  func (req *RPCRequest) SetMethodAndParams(method string, params interface{}) error {
   168  	payload, err := json.Marshal(params)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	req.Method = method
   173  	req.Params = payload
   174  	return nil
   175  }
   176  
   177  //----------------------------------------
   178  // RESPONSE
   179  
   180  type RPCError struct {
   181  	Code    int    `json:"code"`
   182  	Message string `json:"message"`
   183  	Data    string `json:"data,omitempty"`
   184  }
   185  
   186  func (err RPCError) Error() string {
   187  	const baseFormat = "RPC error %v - %s"
   188  	if err.Data != "" {
   189  		return fmt.Sprintf(baseFormat+": %s", err.Code, err.Message, err.Data)
   190  	}
   191  	return fmt.Sprintf(baseFormat, err.Code, err.Message)
   192  }
   193  
   194  type RPCResponse struct {
   195  	id json.RawMessage
   196  
   197  	Result json.RawMessage
   198  	Error  *RPCError
   199  }
   200  
   201  // ID returns a representation of the response ID.
   202  func (resp RPCResponse) ID() string { return string(resp.id) }
   203  
   204  type rpcResponseJSON struct {
   205  	V  string          `json:"jsonrpc"` // must be "2.0"
   206  	ID json.RawMessage `json:"id,omitempty"`
   207  	R  json.RawMessage `json:"result,omitempty"`
   208  	E  *RPCError       `json:"error,omitempty"`
   209  }
   210  
   211  // UnmarshalJSON decodes a response from a JSON-RPC 2.0 response object.
   212  func (resp *RPCResponse) UnmarshalJSON(data []byte) error {
   213  	var wrapper rpcResponseJSON
   214  	if err := json.Unmarshal(data, &wrapper); err != nil {
   215  		return err
   216  	} else if wrapper.V != "" && wrapper.V != "2.0" {
   217  		return fmt.Errorf("invalid version: %q", wrapper.V)
   218  	}
   219  
   220  	if !isNullOrEmpty(wrapper.ID) {
   221  		if !validID.Match(wrapper.ID) {
   222  			return fmt.Errorf("invalid response ID: %q", string(wrapper.ID))
   223  		}
   224  		resp.id = wrapper.ID
   225  	}
   226  	resp.Error = wrapper.E
   227  	resp.Result = wrapper.R
   228  	return nil
   229  }
   230  
   231  // MarshalJSON marshals a response with the appropriate version tag.
   232  func (resp RPCResponse) MarshalJSON() ([]byte, error) {
   233  	return json.Marshal(rpcResponseJSON{
   234  		V:  "2.0",
   235  		ID: resp.id,
   236  		R:  resp.Result,
   237  		E:  resp.Error,
   238  	})
   239  }
   240  
   241  func (resp RPCResponse) String() string {
   242  	if resp.Error == nil {
   243  		return fmt.Sprintf("RPCResponse{%s %X}", resp.ID(), resp.Result)
   244  	}
   245  	return fmt.Sprintf("RPCResponse{%s %v}", resp.ID(), resp.Error)
   246  }
   247  
   248  //----------------------------------------
   249  
   250  // WSRPCConnection represents a websocket connection.
   251  type WSRPCConnection interface {
   252  	// GetRemoteAddr returns a remote address of the connection.
   253  	GetRemoteAddr() string
   254  	// WriteRPCResponse writes the response onto connection (BLOCKING).
   255  	WriteRPCResponse(context.Context, RPCResponse) error
   256  	// TryWriteRPCResponse tries to write the response onto connection (NON-BLOCKING).
   257  	TryWriteRPCResponse(context.Context, RPCResponse) bool
   258  	// Context returns the connection's context.
   259  	Context() context.Context
   260  }
   261  
   262  // CallInfo carries JSON-RPC request metadata for RPC functions invoked via
   263  // JSON-RPC. It can be recovered from the context with GetCallInfo.
   264  type CallInfo struct {
   265  	RPCRequest  *RPCRequest     // non-nil for requests via HTTP or websocket
   266  	HTTPRequest *http.Request   // non-nil for requests via HTTP
   267  	WSConn      WSRPCConnection // non-nil for requests via websocket
   268  }
   269  
   270  type callInfoKey struct{}
   271  
   272  // WithCallInfo returns a child context of ctx with the ci attached.
   273  func WithCallInfo(ctx context.Context, ci *CallInfo) context.Context {
   274  	return context.WithValue(ctx, callInfoKey{}, ci)
   275  }
   276  
   277  // GetCallInfo returns the CallInfo record attached to ctx, or nil if ctx does
   278  // not contain a call record.
   279  func GetCallInfo(ctx context.Context) *CallInfo {
   280  	if v := ctx.Value(callInfoKey{}); v != nil {
   281  		return v.(*CallInfo)
   282  	}
   283  	return nil
   284  }
   285  
   286  // RemoteAddr returns the remote address (usually a string "IP:port").  If
   287  // neither HTTPRequest nor WSConn is set, an empty string is returned.
   288  //
   289  // For HTTP requests, this reports the request's RemoteAddr.
   290  // For websocket requests, this reports the connection's GetRemoteAddr.
   291  func (ci *CallInfo) RemoteAddr() string {
   292  	if ci == nil {
   293  		return ""
   294  	} else if ci.HTTPRequest != nil {
   295  		return ci.HTTPRequest.RemoteAddr
   296  	} else if ci.WSConn != nil {
   297  		return ci.WSConn.GetRemoteAddr()
   298  	}
   299  	return ""
   300  }
   301  
   302  //----------------------------------------
   303  // SOCKETS
   304  
   305  // Determine if its a unix or tcp socket.
   306  // If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port
   307  // TODO: deprecate
   308  func SocketType(listenAddr string) string {
   309  	socketType := "unix"
   310  	if len(strings.Split(listenAddr, ":")) >= 2 {
   311  		socketType = "tcp"
   312  	}
   313  	return socketType
   314  }