| 
									
										
										
										
											2019-07-22 12:17:27 +03:00
										 |  |  | // Copyright 2019 The go-ethereum Authors | 
					
						
							| 
									
										
										
										
											2019-02-04 13:47:34 +01:00
										 |  |  | // This file is part of the go-ethereum library. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package rpc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"reflect" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"unicode" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	contextType      = reflect.TypeOf((*context.Context)(nil)).Elem() | 
					
						
							|  |  |  | 	errorType        = reflect.TypeOf((*error)(nil)).Elem() | 
					
						
							|  |  |  | 	subscriptionType = reflect.TypeOf(Subscription{}) | 
					
						
							|  |  |  | 	stringType       = reflect.TypeOf("") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type serviceRegistry struct { | 
					
						
							|  |  |  | 	mu       sync.Mutex | 
					
						
							|  |  |  | 	services map[string]service | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // service represents a registered object. | 
					
						
							|  |  |  | type service struct { | 
					
						
							|  |  |  | 	name          string               // name for service | 
					
						
							|  |  |  | 	callbacks     map[string]*callback // registered handlers | 
					
						
							|  |  |  | 	subscriptions map[string]*callback // available subscriptions/notifications | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // callback is a method callback which was registered in the server | 
					
						
							|  |  |  | type callback struct { | 
					
						
							|  |  |  | 	fn          reflect.Value  // the function | 
					
						
							|  |  |  | 	rcvr        reflect.Value  // receiver object of method, set if fn is method | 
					
						
							|  |  |  | 	argTypes    []reflect.Type // input argument types | 
					
						
							|  |  |  | 	hasCtx      bool           // method's first argument is a context (not included in argTypes) | 
					
						
							|  |  |  | 	errPos      int            // err return idx, of -1 when method cannot return error | 
					
						
							|  |  |  | 	isSubscribe bool           // true if this is a subscription callback | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { | 
					
						
							|  |  |  | 	rcvrVal := reflect.ValueOf(rcvr) | 
					
						
							|  |  |  | 	if name == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("no service name for type %s", rcvrVal.Type().String()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	callbacks := suitableCallbacks(rcvrVal) | 
					
						
							|  |  |  | 	if len(callbacks) == 0 { | 
					
						
							|  |  |  | 		return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r.mu.Lock() | 
					
						
							|  |  |  | 	defer r.mu.Unlock() | 
					
						
							|  |  |  | 	if r.services == nil { | 
					
						
							|  |  |  | 		r.services = make(map[string]service) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	svc, ok := r.services[name] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		svc = service{ | 
					
						
							|  |  |  | 			name:          name, | 
					
						
							|  |  |  | 			callbacks:     make(map[string]*callback), | 
					
						
							|  |  |  | 			subscriptions: make(map[string]*callback), | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		r.services[name] = svc | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for name, cb := range callbacks { | 
					
						
							|  |  |  | 		if cb.isSubscribe { | 
					
						
							|  |  |  | 			svc.subscriptions[name] = cb | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			svc.callbacks[name] = cb | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // callback returns the callback corresponding to the given RPC method name. | 
					
						
							|  |  |  | func (r *serviceRegistry) callback(method string) *callback { | 
					
						
							|  |  |  | 	elem := strings.SplitN(method, serviceMethodSeparator, 2) | 
					
						
							|  |  |  | 	if len(elem) != 2 { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.mu.Lock() | 
					
						
							|  |  |  | 	defer r.mu.Unlock() | 
					
						
							|  |  |  | 	return r.services[elem[0]].callbacks[elem[1]] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // subscription returns a subscription callback in the given service. | 
					
						
							|  |  |  | func (r *serviceRegistry) subscription(service, name string) *callback { | 
					
						
							|  |  |  | 	r.mu.Lock() | 
					
						
							|  |  |  | 	defer r.mu.Unlock() | 
					
						
							|  |  |  | 	return r.services[service].subscriptions[name] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // suitableCallbacks iterates over the methods of the given type. It determines if a method | 
					
						
							|  |  |  | // satisfies the criteria for a RPC callback or a subscription callback and adds it to the | 
					
						
							|  |  |  | // collection of callbacks. See server documentation for a summary of these criteria. | 
					
						
							|  |  |  | func suitableCallbacks(receiver reflect.Value) map[string]*callback { | 
					
						
							|  |  |  | 	typ := receiver.Type() | 
					
						
							|  |  |  | 	callbacks := make(map[string]*callback) | 
					
						
							|  |  |  | 	for m := 0; m < typ.NumMethod(); m++ { | 
					
						
							|  |  |  | 		method := typ.Method(m) | 
					
						
							|  |  |  | 		if method.PkgPath != "" { | 
					
						
							|  |  |  | 			continue // method not exported | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		cb := newCallback(receiver, method.Func) | 
					
						
							|  |  |  | 		if cb == nil { | 
					
						
							|  |  |  | 			continue // function invalid | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		name := formatName(method.Name) | 
					
						
							|  |  |  | 		callbacks[name] = cb | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return callbacks | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newCallback turns fn (a function) into a callback object. It returns nil if the function | 
					
						
							|  |  |  | // is unsuitable as an RPC callback. | 
					
						
							|  |  |  | func newCallback(receiver, fn reflect.Value) *callback { | 
					
						
							|  |  |  | 	fntype := fn.Type() | 
					
						
							|  |  |  | 	c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)} | 
					
						
							|  |  |  | 	// Determine parameter types. They must all be exported or builtin types. | 
					
						
							|  |  |  | 	c.makeArgTypes() | 
					
						
							| 
									
										
										
										
											2019-11-20 09:06:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-04 13:47:34 +01:00
										 |  |  | 	// Verify return types. The function must return at most one error | 
					
						
							|  |  |  | 	// and/or one other non-error value. | 
					
						
							|  |  |  | 	outs := make([]reflect.Type, fntype.NumOut()) | 
					
						
							|  |  |  | 	for i := 0; i < fntype.NumOut(); i++ { | 
					
						
							|  |  |  | 		outs[i] = fntype.Out(i) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-11-20 09:06:21 +01:00
										 |  |  | 	if len(outs) > 2 { | 
					
						
							| 
									
										
										
										
											2019-02-04 13:47:34 +01:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// If an error is returned, it must be the last returned value. | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case len(outs) == 1 && isErrorType(outs[0]): | 
					
						
							|  |  |  | 		c.errPos = 0 | 
					
						
							|  |  |  | 	case len(outs) == 2: | 
					
						
							|  |  |  | 		if isErrorType(outs[0]) || !isErrorType(outs[1]) { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		c.errPos = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return c | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // makeArgTypes composes the argTypes list. | 
					
						
							|  |  |  | func (c *callback) makeArgTypes() { | 
					
						
							|  |  |  | 	fntype := c.fn.Type() | 
					
						
							|  |  |  | 	// Skip receiver and context.Context parameter (if present). | 
					
						
							|  |  |  | 	firstArg := 0 | 
					
						
							|  |  |  | 	if c.rcvr.IsValid() { | 
					
						
							|  |  |  | 		firstArg++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { | 
					
						
							|  |  |  | 		c.hasCtx = true | 
					
						
							|  |  |  | 		firstArg++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Add all remaining parameters. | 
					
						
							|  |  |  | 	c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg) | 
					
						
							|  |  |  | 	for i := firstArg; i < fntype.NumIn(); i++ { | 
					
						
							|  |  |  | 		c.argTypes[i-firstArg] = fntype.In(i) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // call invokes the callback. | 
					
						
							|  |  |  | func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) { | 
					
						
							|  |  |  | 	// Create the argument slice. | 
					
						
							|  |  |  | 	fullargs := make([]reflect.Value, 0, 2+len(args)) | 
					
						
							|  |  |  | 	if c.rcvr.IsValid() { | 
					
						
							|  |  |  | 		fullargs = append(fullargs, c.rcvr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if c.hasCtx { | 
					
						
							|  |  |  | 		fullargs = append(fullargs, reflect.ValueOf(ctx)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	fullargs = append(fullargs, args...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Catch panic while running the callback. | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if err := recover(); err != nil { | 
					
						
							|  |  |  | 			const size = 64 << 10 | 
					
						
							|  |  |  | 			buf := make([]byte, size) | 
					
						
							|  |  |  | 			buf = buf[:runtime.Stack(buf, false)] | 
					
						
							|  |  |  | 			log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) | 
					
						
							|  |  |  | 			errRes = errors.New("method handler crashed") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	// Run the callback. | 
					
						
							|  |  |  | 	results := c.fn.Call(fullargs) | 
					
						
							|  |  |  | 	if len(results) == 0 { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if c.errPos >= 0 && !results[c.errPos].IsNil() { | 
					
						
							|  |  |  | 		// Method has returned non-nil error value. | 
					
						
							|  |  |  | 		err := results[c.errPos].Interface().(error) | 
					
						
							|  |  |  | 		return reflect.Value{}, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return results[0].Interface(), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Is t context.Context or *context.Context? | 
					
						
							|  |  |  | func isContextType(t reflect.Type) bool { | 
					
						
							|  |  |  | 	for t.Kind() == reflect.Ptr { | 
					
						
							|  |  |  | 		t = t.Elem() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return t == contextType | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Does t satisfy the error interface? | 
					
						
							|  |  |  | func isErrorType(t reflect.Type) bool { | 
					
						
							|  |  |  | 	for t.Kind() == reflect.Ptr { | 
					
						
							|  |  |  | 		t = t.Elem() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return t.Implements(errorType) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Is t Subscription or *Subscription? | 
					
						
							|  |  |  | func isSubscriptionType(t reflect.Type) bool { | 
					
						
							|  |  |  | 	for t.Kind() == reflect.Ptr { | 
					
						
							|  |  |  | 		t = t.Elem() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return t == subscriptionType | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // isPubSub tests whether the given method has as as first argument a context.Context and | 
					
						
							|  |  |  | // returns the pair (Subscription, error). | 
					
						
							|  |  |  | func isPubSub(methodType reflect.Type) bool { | 
					
						
							|  |  |  | 	// numIn(0) is the receiver type | 
					
						
							|  |  |  | 	if methodType.NumIn() < 2 || methodType.NumOut() != 2 { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return isContextType(methodType.In(1)) && | 
					
						
							|  |  |  | 		isSubscriptionType(methodType.Out(0)) && | 
					
						
							|  |  |  | 		isErrorType(methodType.Out(1)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // formatName converts to first character of name to lowercase. | 
					
						
							|  |  |  | func formatName(name string) string { | 
					
						
							|  |  |  | 	ret := []rune(name) | 
					
						
							|  |  |  | 	if len(ret) > 0 { | 
					
						
							|  |  |  | 		ret[0] = unicode.ToLower(ret[0]) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return string(ret) | 
					
						
							|  |  |  | } |