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 }