github.com/ethereum/go-ethereum@v1.14.3/rpc/service.go (about)

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