github.com/MetalBlockchain/subnet-evm@v0.4.9/rpc/service.go (about)

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