github.com/klaytn/klaytn@v1.12.1/networks/rpc/service.go (about) 1 // Modifications Copyright 2022 The klaytn Authors 2 // Copyright 2022 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from rpc/service.go (2022/08/04). 19 // Modified and improved for the klaytn development. 20 21 package rpc 22 23 import ( 24 "context" 25 "errors" 26 "fmt" 27 "reflect" 28 "runtime" 29 "strings" 30 "sync" 31 "unicode" 32 "unicode/utf8" 33 ) 34 35 var ( 36 contextType = reflect.TypeOf((*context.Context)(nil)).Elem() 37 errorType = reflect.TypeOf((*error)(nil)).Elem() 38 subscriptionType = reflect.TypeOf(Subscription{}) 39 stringType = reflect.TypeOf("") 40 ) 41 42 type serviceRegistry struct { 43 mu sync.Mutex 44 services map[string]service 45 } 46 47 // service represents a registered object. 48 type service struct { 49 name string // name for service 50 callbacks map[string]*callback // registered handlers 51 subscriptions map[string]*callback // available subscriptions/notifications 52 } 53 54 // callback is a method callback which was registered in the server 55 type callback struct { 56 fn reflect.Value // the function 57 rcvr reflect.Value // receiver object of method, set if fn is method 58 argTypes []reflect.Type // input argument types 59 errPos int // err return idx, of -1 when method cannot return error 60 hasCtx bool // method's first argument is a context (not included in argTypes) 61 isSubscribe bool // true if this is a subscription callback 62 } 63 64 func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { 65 rcvrVal := reflect.ValueOf(rcvr) 66 if name == "" { 67 return fmt.Errorf("no service name for type %s", rcvrVal.Type().String()) 68 } 69 callbacks := suitableCallbacks(rcvrVal) 70 if len(callbacks) == 0 { 71 return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr) 72 } 73 74 r.mu.Lock() 75 defer r.mu.Unlock() 76 if r.services == nil { 77 r.services = make(map[string]service) 78 } 79 svc, ok := r.services[name] 80 if !ok { 81 svc = service{ 82 name: name, 83 callbacks: make(map[string]*callback), 84 subscriptions: make(map[string]*callback), 85 } 86 r.services[name] = svc 87 } 88 for name, cb := range callbacks { 89 if cb.isSubscribe { 90 svc.subscriptions[name] = cb 91 } else { 92 svc.callbacks[name] = cb 93 } 94 } 95 return nil 96 } 97 98 // callback returns the callback corresponding to the given RPC method name. 99 func (r *serviceRegistry) callback(method string) *callback { 100 elem := strings.SplitN(method, serviceMethodSeparator, 2) 101 if len(elem) != 2 { 102 return nil 103 } 104 r.mu.Lock() 105 defer r.mu.Unlock() 106 107 // when NonEthCompatible is true, the return formatting for the eth namespace API provided for Ethereum compatibility is disabled. 108 // convert ethereum namespace to klay namespace. 109 if NonEthCompatible && elem[0] == "eth" { 110 return r.services["klay"].callbacks[elem[1]] 111 } 112 return r.services[elem[0]].callbacks[elem[1]] 113 } 114 115 // subscription returns a subscription callback in the given service. 116 func (r *serviceRegistry) subscription(service, name string) *callback { 117 r.mu.Lock() 118 defer r.mu.Unlock() 119 return r.services[service].subscriptions[name] 120 } 121 122 // suitableCallbacks iterates over the methods of the given type. It determines if a method 123 // satisfies the criteria for a RPC callback or a subscription callback and adds it to the 124 // collection of callbacks. See server documentation for a summary of these criteria. 125 func suitableCallbacks(receiver reflect.Value) map[string]*callback { 126 typ := receiver.Type() 127 callbacks := make(map[string]*callback) 128 for m := 0; m < typ.NumMethod(); m++ { 129 method := typ.Method(m) 130 if method.PkgPath != "" { 131 continue // method not exported 132 } 133 cb := newCallback(receiver, method.Func) 134 if cb == nil { 135 continue // function invalid 136 } 137 name := formatName(method.Name) 138 callbacks[name] = cb 139 } 140 return callbacks 141 } 142 143 // newCallback turns fn (a function) into a callback object. It returns nil if the function 144 // is unsuitable as an RPC callback. 145 func newCallback(receiver, fn reflect.Value) *callback { 146 fntype := fn.Type() 147 c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)} 148 // Determine parameter types. They must all be exported or builtin types. 149 c.makeArgTypes() 150 if !allExportedOrBuiltin(c.argTypes) { 151 return nil 152 } 153 // Verify return types. The function must return at most one error 154 // and/or one other non-error value. 155 outs := make([]reflect.Type, fntype.NumOut()) 156 for i := 0; i < fntype.NumOut(); i++ { 157 outs[i] = fntype.Out(i) 158 } 159 if len(outs) > 2 || !allExportedOrBuiltin(outs) { 160 return nil 161 } 162 // If an error is returned, it must be the last returned value. 163 switch { 164 case len(outs) == 1 && isErrorType(outs[0]): 165 c.errPos = 0 166 case len(outs) == 2: 167 if isErrorType(outs[0]) || !isErrorType(outs[1]) { 168 return nil 169 } 170 c.errPos = 1 171 } 172 return c 173 } 174 175 // makeArgTypes composes the argTypes list. 176 func (c *callback) makeArgTypes() { 177 fntype := c.fn.Type() 178 // Skip receiver and context.Context parameter (if present). 179 firstArg := 0 180 if c.rcvr.IsValid() { 181 firstArg++ 182 } 183 if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { 184 c.hasCtx = true 185 firstArg++ 186 } 187 // Add all remaining parameters. 188 c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg) 189 for i := firstArg; i < fntype.NumIn(); i++ { 190 c.argTypes[i-firstArg] = fntype.In(i) 191 } 192 } 193 194 // call invokes the callback. 195 func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) { 196 // Create the argument slice. 197 fullargs := make([]reflect.Value, 0, 2+len(args)) 198 if c.rcvr.IsValid() { 199 fullargs = append(fullargs, c.rcvr) 200 } 201 if c.hasCtx { 202 fullargs = append(fullargs, reflect.ValueOf(ctx)) 203 } 204 fullargs = append(fullargs, args...) 205 206 // Catch panic while running the callback. 207 defer func() { 208 if err := recover(); err != nil { 209 const size = 64 << 10 210 buf := make([]byte, size) 211 buf = buf[:runtime.Stack(buf, false)] 212 logger.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) 213 errRes = errors.New("method handler crashed") 214 } 215 }() 216 // Run the callback. 217 results := c.fn.Call(fullargs) 218 if len(results) == 0 { 219 return nil, nil 220 } 221 if c.errPos >= 0 && !results[c.errPos].IsNil() { 222 // Method has returned non-nil error value. 223 err := results[c.errPos].Interface().(error) 224 return reflect.Value{}, err 225 } 226 return results[0].Interface(), nil 227 } 228 229 // Is this an exported - upper case - name? 230 func isExported(name string) bool { 231 rune, _ := utf8.DecodeRuneInString(name) 232 return unicode.IsUpper(rune) 233 } 234 235 // Are all those types exported or built-in? 236 func allExportedOrBuiltin(types []reflect.Type) bool { 237 for _, typ := range types { 238 for typ.Kind() == reflect.Ptr { 239 typ = typ.Elem() 240 } 241 // PkgPath will be non-empty even for an exported type, 242 // so we need to check the type name as well. 243 if !isExported(typ.Name()) && typ.PkgPath() != "" { 244 return false 245 } 246 } 247 return true 248 } 249 250 // Is t context.Context or *context.Context? 251 func isContextType(t reflect.Type) bool { 252 for t.Kind() == reflect.Ptr { 253 t = t.Elem() 254 } 255 return t == contextType 256 } 257 258 // Does t satisfy the error interface? 259 func isErrorType(t reflect.Type) bool { 260 for t.Kind() == reflect.Ptr { 261 t = t.Elem() 262 } 263 return t.Implements(errorType) 264 } 265 266 // Is t Subscription or *Subscription? 267 func isSubscriptionType(t reflect.Type) bool { 268 for t.Kind() == reflect.Ptr { 269 t = t.Elem() 270 } 271 return t == subscriptionType 272 } 273 274 // isPubSub tests whether the given method has as as first argument a context.Context and 275 // returns the pair (Subscription, error). 276 func isPubSub(methodType reflect.Type) bool { 277 // numIn(0) is the receiver type 278 if methodType.NumIn() < 2 || methodType.NumOut() != 2 { 279 return false 280 } 281 return isContextType(methodType.In(1)) && 282 isSubscriptionType(methodType.Out(0)) && 283 isErrorType(methodType.Out(1)) 284 } 285 286 // formatName converts to first character of name to lowercase. 287 func formatName(name string) string { 288 ret := []rune(name) 289 if len(ret) > 0 { 290 ret[0] = unicode.ToLower(ret[0]) 291 } 292 return string(ret) 293 }