github.com/0xPolygon/supernets2-node@v0.0.0-20230711153321-2fe574524eaa/jsonrpc/handler.go (about)

     1  package jsonrpc
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"reflect"
     8  	"strings"
     9  	"sync"
    10  	"unicode"
    11  
    12  	"github.com/0xPolygon/supernets2-node/jsonrpc/types"
    13  	"github.com/0xPolygon/supernets2-node/log"
    14  	"github.com/gorilla/websocket"
    15  )
    16  
    17  const (
    18  	requiredReturnParamsPerFn = 2
    19  )
    20  
    21  type serviceData struct {
    22  	sv      reflect.Value
    23  	funcMap map[string]*funcData
    24  }
    25  
    26  type funcData struct {
    27  	inNum int
    28  	reqt  []reflect.Type
    29  	fv    reflect.Value
    30  	isDyn bool
    31  }
    32  
    33  func (f *funcData) numParams() int {
    34  	return f.inNum - 1
    35  }
    36  
    37  type handleRequest struct {
    38  	types.Request
    39  	wsConn      *websocket.Conn
    40  	HttpRequest *http.Request
    41  }
    42  
    43  // Handler manage services to handle jsonrpc requests
    44  //
    45  // Services are public structures containing public methods
    46  // matching the name of the jsonrpc method.
    47  //
    48  // Services must be registered with a prefix to identify the
    49  // service and its methods, for example a service registered
    50  // with a prefix `eth` will have all the public methods exposed
    51  // as eth_<methodName> through the json rpc server.
    52  //
    53  // Go public methods requires the first char of its name to be
    54  // in uppercase, but the exposition of the method will consider
    55  // it to lower case, for example a method `func MyMethod()`
    56  // provided by the service registered with `eth` prefix will
    57  // be triggered when the method eth_myMethod is specified
    58  //
    59  // the public methods must follow the conventions:
    60  // - return interface{}, rpcError
    61  // - if the method depend on a Web Socket connection, it must be the first parameters as f(*websocket.Conn)
    62  // - parameter types must match the type of the data provided for the method
    63  //
    64  // check the `eth.go` file for more example on how the methods are implemented
    65  type Handler struct {
    66  	serviceMap map[string]*serviceData
    67  }
    68  
    69  func newJSONRpcHandler() *Handler {
    70  	handler := &Handler{
    71  		serviceMap: map[string]*serviceData{},
    72  	}
    73  	return handler
    74  }
    75  
    76  var connectionCounter = 0
    77  var connectionCounterMutex sync.Mutex
    78  
    79  // Handle is the function that knows which and how a function should
    80  // be executed when a JSON RPC request is received
    81  func (h *Handler) Handle(req handleRequest) types.Response {
    82  	log := log.WithFields("method", req.Method, "requestId", req.ID)
    83  	connectionCounterMutex.Lock()
    84  	connectionCounter++
    85  	connectionCounterMutex.Unlock()
    86  	defer func() {
    87  		connectionCounterMutex.Lock()
    88  		connectionCounter--
    89  		connectionCounterMutex.Unlock()
    90  		log.Debugf("Current open connections %d", connectionCounter)
    91  	}()
    92  	log.Debugf("Current open connections %d", connectionCounter)
    93  	log.Debugf("request params %v", string(req.Params))
    94  
    95  	service, fd, err := h.getFnHandler(req.Request)
    96  	if err != nil {
    97  		return types.NewResponse(req.Request, nil, err)
    98  	}
    99  
   100  	inArgsOffset := 0
   101  	inArgs := make([]reflect.Value, fd.inNum)
   102  	inArgs[0] = service.sv
   103  
   104  	requestHasWebSocketConn := req.wsConn != nil
   105  	funcHasMoreThanOneInputParams := len(fd.reqt) > 1
   106  	firstFuncParamIsWebSocketConn := false
   107  	firstFuncParamIsHttpRequest := false
   108  	if funcHasMoreThanOneInputParams {
   109  		firstFuncParamIsWebSocketConn = fd.reqt[1].AssignableTo(reflect.TypeOf(&websocket.Conn{}))
   110  		firstFuncParamIsHttpRequest = fd.reqt[1].AssignableTo(reflect.TypeOf(&http.Request{}))
   111  	}
   112  	if requestHasWebSocketConn && firstFuncParamIsWebSocketConn {
   113  		inArgs[1] = reflect.ValueOf(req.wsConn)
   114  		inArgsOffset++
   115  	} else if firstFuncParamIsHttpRequest {
   116  		// If in the future one endponit needs to have both a websocket connection and an http request
   117  		// we will need to modify this code to properly handle it
   118  		inArgs[1] = reflect.ValueOf(req.HttpRequest)
   119  		inArgsOffset++
   120  	}
   121  
   122  	// check params passed by request match function params
   123  	var testStruct []interface{}
   124  	if err := json.Unmarshal(req.Params, &testStruct); err == nil && len(testStruct) > fd.numParams() {
   125  		return types.NewResponse(req.Request, nil, types.NewRPCError(types.InvalidParamsErrorCode, fmt.Sprintf("too many arguments, want at most %d", fd.numParams())))
   126  	}
   127  
   128  	inputs := make([]interface{}, fd.numParams()-inArgsOffset)
   129  
   130  	for i := inArgsOffset; i < fd.inNum-1; i++ {
   131  		val := reflect.New(fd.reqt[i+1])
   132  		inputs[i-inArgsOffset] = val.Interface()
   133  		inArgs[i+1] = val.Elem()
   134  	}
   135  
   136  	if fd.numParams() > 0 {
   137  		if err := json.Unmarshal(req.Params, &inputs); err != nil {
   138  			return types.NewResponse(req.Request, nil, types.NewRPCError(types.InvalidParamsErrorCode, "Invalid Params"))
   139  		}
   140  	}
   141  
   142  	output := fd.fv.Call(inArgs)
   143  	if err := getError(output[1]); err != nil {
   144  		log.Infof("failed call: [%v]%v. Params: %v", err.ErrorCode(), err.Error(), string(req.Params))
   145  		return types.NewResponse(req.Request, nil, err)
   146  	}
   147  
   148  	var data []byte
   149  	res := output[0].Interface()
   150  	if res != nil {
   151  		d, _ := json.Marshal(res)
   152  		data = d
   153  	}
   154  
   155  	return types.NewResponse(req.Request, data, nil)
   156  }
   157  
   158  // HandleWs handle websocket requests
   159  func (h *Handler) HandleWs(reqBody []byte, wsConn *websocket.Conn) ([]byte, error) {
   160  	var req types.Request
   161  	if err := json.Unmarshal(reqBody, &req); err != nil {
   162  		return types.NewResponse(req, nil, types.NewRPCError(types.InvalidRequestErrorCode, "Invalid json request")).Bytes()
   163  	}
   164  
   165  	handleReq := handleRequest{
   166  		Request: req,
   167  		wsConn:  wsConn,
   168  	}
   169  
   170  	return h.Handle(handleReq).Bytes()
   171  }
   172  
   173  // RemoveFilterByWsConn uninstalls the filter attached to this websocket connection
   174  func (h *Handler) RemoveFilterByWsConn(wsConn *websocket.Conn) {
   175  	service, ok := h.serviceMap[APIEth]
   176  	if !ok {
   177  		return
   178  	}
   179  
   180  	ethEndpointsInterface := service.sv.Interface()
   181  	if ethEndpointsInterface == nil {
   182  		log.Errorf("failed to get ETH endpoint interface")
   183  	}
   184  
   185  	ethEndpoints := ethEndpointsInterface.(*EthEndpoints)
   186  	if ethEndpoints == nil {
   187  		log.Errorf("failed to get ETH endpoint instance")
   188  		return
   189  	}
   190  
   191  	err := ethEndpoints.uninstallFilterByWSConn(wsConn)
   192  	if err != nil {
   193  		log.Errorf("failed to uninstall filter by web socket connection:, %v", err)
   194  		return
   195  	}
   196  }
   197  
   198  func (h *Handler) registerService(service Service) {
   199  	st := reflect.TypeOf(service.Service)
   200  	if st.Kind() == reflect.Struct {
   201  		panic(fmt.Sprintf("jsonrpc: service '%s' must be a pointer to struct", service.Name))
   202  	}
   203  
   204  	funcMap := make(map[string]*funcData)
   205  	for i := 0; i < st.NumMethod(); i++ {
   206  		mv := st.Method(i)
   207  		if mv.PkgPath != "" {
   208  			// skip unexported methods
   209  			continue
   210  		}
   211  
   212  		name := lowerCaseFirst(mv.Name)
   213  		funcName := service.Name + "_" + name
   214  		fd := &funcData{
   215  			fv: mv.Func,
   216  		}
   217  		var err error
   218  		if fd.inNum, fd.reqt, err = validateFunc(funcName, fd.fv, true); err != nil {
   219  			panic(fmt.Sprintf("jsonrpc: %s", err))
   220  		}
   221  		// check if last item is a pointer
   222  		if fd.numParams() != 0 {
   223  			last := fd.reqt[fd.numParams()]
   224  			if last.Kind() == reflect.Ptr {
   225  				fd.isDyn = true
   226  			}
   227  		}
   228  		funcMap[name] = fd
   229  	}
   230  
   231  	h.serviceMap[service.Name] = &serviceData{
   232  		sv:      reflect.ValueOf(service.Service),
   233  		funcMap: funcMap,
   234  	}
   235  }
   236  
   237  func (h *Handler) getFnHandler(req types.Request) (*serviceData, *funcData, types.Error) {
   238  	methodNotFoundErrorMessage := fmt.Sprintf("the method %s does not exist/is not available", req.Method)
   239  
   240  	callName := strings.SplitN(req.Method, "_", 2) //nolint:gomnd
   241  	if len(callName) != 2 {                        //nolint:gomnd
   242  		return nil, nil, types.NewRPCError(types.NotFoundErrorCode, methodNotFoundErrorMessage)
   243  	}
   244  
   245  	serviceName, funcName := callName[0], callName[1]
   246  
   247  	service, ok := h.serviceMap[serviceName]
   248  	if !ok {
   249  		log.Infof("Method %s not found", req.Method)
   250  		return nil, nil, types.NewRPCError(types.NotFoundErrorCode, methodNotFoundErrorMessage)
   251  	}
   252  	fd, ok := service.funcMap[funcName]
   253  	if !ok {
   254  		return nil, nil, types.NewRPCError(types.NotFoundErrorCode, methodNotFoundErrorMessage)
   255  	}
   256  	return service, fd, nil
   257  }
   258  
   259  func validateFunc(funcName string, fv reflect.Value, isMethod bool) (inNum int, reqt []reflect.Type, err error) {
   260  	if funcName == "" {
   261  		err = fmt.Errorf("getBlockNumByArg cannot be empty")
   262  		return
   263  	}
   264  
   265  	ft := fv.Type()
   266  	if ft.Kind() != reflect.Func {
   267  		err = fmt.Errorf("function '%s' must be a function instead of %s", funcName, ft)
   268  		return
   269  	}
   270  
   271  	inNum = ft.NumIn()
   272  	outNum := ft.NumOut()
   273  
   274  	if outNum != requiredReturnParamsPerFn {
   275  		err = fmt.Errorf("unexpected number of output arguments in the function '%s': %d. Expected 2", funcName, outNum)
   276  		return
   277  	}
   278  	if !isRPCErrorType(ft.Out(1)) {
   279  		err = fmt.Errorf("unexpected type for the second return value of the function '%s': '%s'. Expected '%s'", funcName, ft.Out(1), rpcErrType)
   280  		return
   281  	}
   282  
   283  	reqt = make([]reflect.Type, inNum)
   284  	for i := 0; i < inNum; i++ {
   285  		reqt[i] = ft.In(i)
   286  	}
   287  	return
   288  }
   289  
   290  var rpcErrType = reflect.TypeOf((*types.Error)(nil)).Elem()
   291  
   292  func isRPCErrorType(t reflect.Type) bool {
   293  	return t.Implements(rpcErrType)
   294  }
   295  
   296  func getError(v reflect.Value) types.Error {
   297  	if v.IsNil() {
   298  		return nil
   299  	}
   300  
   301  	switch vt := v.Interface().(type) {
   302  	case *types.RPCError:
   303  		return vt
   304  	default:
   305  		return types.NewRPCError(types.DefaultErrorCode, "runtime error")
   306  	}
   307  }
   308  
   309  func lowerCaseFirst(str string) string {
   310  	for i, v := range str {
   311  		return string(unicode.ToLower(v)) + str[i+1:]
   312  	}
   313  	return ""
   314  }