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  }