github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/server/rpc_func.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net/http" 10 "reflect" 11 "strings" 12 "time" 13 14 "github.com/ari-anchor/sei-tendermint/libs/log" 15 rpctypes "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/types" 16 ) 17 18 // DefaultRPCTimeout is the default context timeout for calls to any RPC method 19 // that does not override it with a more specific timeout. 20 const DefaultRPCTimeout = 60 * time.Second 21 22 // RegisterRPCFuncs adds a route to mux for each non-websocket function in the 23 // funcMap, and also a root JSON-RPC POST handler. 24 func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger log.Logger) { 25 for name, fn := range funcMap { 26 if fn.ws { 27 continue // skip websocket endpoints, not usable via GET calls 28 } 29 mux.HandleFunc("/"+name, ensureBodyClose(makeHTTPHandler(fn, logger))) 30 } 31 32 // Endpoints for POST. 33 mux.HandleFunc("/", ensureBodyClose(handleInvalidJSONRPCPaths(makeJSONRPCHandler(funcMap, logger)))) 34 } 35 36 // Function introspection 37 38 // RPCFunc contains the introspected type information for a function. 39 type RPCFunc struct { 40 f reflect.Value // underlying rpc function 41 param reflect.Type // the parameter struct, or nil 42 result reflect.Type // the non-error result type, or nil 43 args []argInfo // names and type information (for URL decoding) 44 timeout time.Duration // default request timeout, 0 means none 45 ws bool // websocket only 46 } 47 48 // argInfo records the name of a field, along with a bit to tell whether the 49 // value of the field requires binary data, having underlying type []byte. The 50 // flag is needed when decoding URL parameters, where we permit quoted strings 51 // to be passed for either argument type. 52 type argInfo struct { 53 name string 54 isBinary bool // value wants binary data 55 } 56 57 // Call parses the given JSON parameters and calls the function wrapped by rf 58 // with the resulting argument value. It reports an error if parameter parsing 59 // fails, otherwise it returns the result from the wrapped function. 60 func (rf *RPCFunc) Call(ctx context.Context, params json.RawMessage) (interface{}, error) { 61 // If ctx has its own deadline we will respect it; otherwise use rf.timeout. 62 if _, ok := ctx.Deadline(); !ok && rf.timeout > 0 { 63 var cancel context.CancelFunc 64 ctx, cancel = context.WithTimeout(ctx, rf.timeout) 65 defer cancel() 66 } 67 args, err := rf.parseParams(ctx, params) 68 if err != nil { 69 return nil, err 70 } 71 returns := rf.f.Call(args) 72 73 // Case 1: There is no non-error result type. 74 if rf.result == nil { 75 if oerr := returns[0].Interface(); oerr != nil { 76 return nil, oerr.(error) 77 } 78 return nil, nil 79 } 80 81 // Case 2: There is a non-error result. 82 if oerr := returns[1].Interface(); oerr != nil { 83 // In case of error, report the error and ignore the result. 84 return nil, oerr.(error) 85 } 86 return returns[0].Interface(), nil 87 } 88 89 // Timeout updates rf to include a default timeout for calls to rf. This 90 // timeout is used if one is not already provided on the request context. 91 // Setting d == 0 means there will be no timeout. Returns rf to allow chaining. 92 func (rf *RPCFunc) Timeout(d time.Duration) *RPCFunc { rf.timeout = d; return rf } 93 94 // parseParams parses the parameters of a JSON-RPC request and returns the 95 // corresponding argument values. On success, the first argument value will be 96 // the value of ctx. 97 func (rf *RPCFunc) parseParams(ctx context.Context, params json.RawMessage) ([]reflect.Value, error) { 98 // If rf does not accept parameters, there is no decoding to do, but verify 99 // that no parameters were passed. 100 if rf.param == nil { 101 if !isNullOrEmpty(params) { 102 return nil, invalidParamsError("no parameters accepted for this method") 103 } 104 return []reflect.Value{reflect.ValueOf(ctx)}, nil 105 } 106 bits, err := rf.adjustParams(params) 107 if err != nil { 108 return nil, invalidParamsError(err.Error()) 109 } 110 arg := reflect.New(rf.param) 111 if err := json.Unmarshal(bits, arg.Interface()); err != nil { 112 return nil, invalidParamsError(err.Error()) 113 } 114 return []reflect.Value{reflect.ValueOf(ctx), arg}, nil 115 } 116 117 // adjustParams checks whether data is encoded as a JSON array, and if so 118 // adjusts the values to match the corresponding parameter names. 119 func (rf *RPCFunc) adjustParams(data []byte) (json.RawMessage, error) { 120 base := bytes.TrimSpace(data) 121 if bytes.HasPrefix(base, []byte("[")) { 122 var args []json.RawMessage 123 if err := json.Unmarshal(base, &args); err != nil { 124 return nil, err 125 } else if len(args) != len(rf.args) { 126 return nil, fmt.Errorf("got %d arguments, want %d", len(args), len(rf.args)) 127 } 128 m := make(map[string]json.RawMessage) 129 for i, arg := range args { 130 m[rf.args[i].name] = arg 131 } 132 return json.Marshal(m) 133 } else if bytes.HasPrefix(base, []byte("{")) || bytes.Equal(base, []byte("null")) { 134 return base, nil 135 } 136 return nil, errors.New("parameters must be an object or an array") 137 138 } 139 140 // NewRPCFunc constructs an RPCFunc for f, which must be a function whose type 141 // signature matches one of these schemes: 142 // 143 // func(context.Context) error 144 // func(context.Context) (R, error) 145 // func(context.Context, *T) error 146 // func(context.Context, *T) (R, error) 147 // 148 // for an arbitrary struct type T and type R. NewRPCFunc will panic if f does 149 // not have one of these forms. A newly-constructed RPCFunc has a default 150 // timeout of DefaultRPCTimeout; use the Timeout method to adjust this as 151 // needed. 152 func NewRPCFunc(f interface{}) *RPCFunc { 153 rf, err := newRPCFunc(f) 154 if err != nil { 155 panic("invalid RPC function: " + err.Error()) 156 } 157 return rf 158 } 159 160 // NewWSRPCFunc behaves as NewRPCFunc, but marks the resulting function for use 161 // via websocket. 162 func NewWSRPCFunc(f interface{}) *RPCFunc { 163 rf := NewRPCFunc(f) 164 rf.ws = true 165 return rf 166 } 167 168 var ( 169 ctxType = reflect.TypeOf((*context.Context)(nil)).Elem() 170 errType = reflect.TypeOf((*error)(nil)).Elem() 171 ) 172 173 // newRPCFunc constructs an RPCFunc for f. See the comment at NewRPCFunc. 174 func newRPCFunc(f interface{}) (*RPCFunc, error) { 175 if f == nil { 176 return nil, errors.New("nil function") 177 } 178 179 // Check the type and signature of f. 180 fv := reflect.ValueOf(f) 181 if fv.Kind() != reflect.Func { 182 return nil, errors.New("not a function") 183 } 184 185 var ptype reflect.Type 186 ft := fv.Type() 187 if np := ft.NumIn(); np == 0 || np > 2 { 188 return nil, errors.New("wrong number of parameters") 189 } else if ft.In(0) != ctxType { 190 return nil, errors.New("first parameter is not context.Context") 191 } else if np == 2 { 192 ptype = ft.In(1) 193 if ptype.Kind() != reflect.Ptr { 194 return nil, errors.New("parameter type is not a pointer") 195 } 196 ptype = ptype.Elem() 197 if ptype.Kind() != reflect.Struct { 198 return nil, errors.New("parameter type is not a struct") 199 } 200 } 201 202 var rtype reflect.Type 203 if no := ft.NumOut(); no < 1 || no > 2 { 204 return nil, errors.New("wrong number of results") 205 } else if ft.Out(no-1) != errType { 206 return nil, errors.New("last result is not error") 207 } else if no == 2 { 208 rtype = ft.Out(0) 209 } 210 211 var args []argInfo 212 if ptype != nil { 213 for i := 0; i < ptype.NumField(); i++ { 214 field := ptype.Field(i) 215 if tag := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]; tag != "" && tag != "-" { 216 args = append(args, argInfo{ 217 name: tag, 218 isBinary: isByteArray(field.Type), 219 }) 220 } else if tag == "-" { 221 // If the tag is "-" the field should explicitly be ignored, even 222 // if it is otherwise eligible. 223 } else if field.IsExported() && !field.Anonymous { 224 // Examples: Name → name, MaxEffort → maxEffort. 225 // Note that this is an aesthetic choice; the standard decoder will 226 // match without regard to case anyway. 227 name := strings.ToLower(field.Name[:1]) + field.Name[1:] 228 args = append(args, argInfo{ 229 name: name, 230 isBinary: isByteArray(field.Type), 231 }) 232 } 233 } 234 } 235 236 return &RPCFunc{ 237 f: fv, 238 param: ptype, 239 result: rtype, 240 args: args, 241 timeout: DefaultRPCTimeout, // until overridden 242 }, nil 243 } 244 245 // invalidParamsError returns an RPC invalid parameters error with the given 246 // detail message. 247 func invalidParamsError(msg string, args ...interface{}) error { 248 return &rpctypes.RPCError{ 249 Code: int(rpctypes.CodeInvalidParams), 250 Message: rpctypes.CodeInvalidParams.String(), 251 Data: fmt.Sprintf(msg, args...), 252 } 253 } 254 255 // isNullOrEmpty reports whether params is either itself empty or represents an 256 // empty parameter (null, empty object, or empty array). 257 func isNullOrEmpty(params json.RawMessage) bool { 258 return len(params) == 0 || 259 bytes.Equal(params, []byte("null")) || 260 bytes.Equal(params, []byte("{}")) || 261 bytes.Equal(params, []byte("[]")) 262 } 263 264 // isByteArray reports whether t is (equivalent to) []byte. 265 func isByteArray(t reflect.Type) bool { 266 return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 267 }