github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/rpc/utils.go (about)

     1  // Copyright 2015 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  	"crypto/rand"
    22  	"encoding/hex"
    23  	"errors"
    24  	"fmt"
    25  	"math/big"
    26  	"reflect"
    27  	"unicode"
    28  	"unicode/utf8"
    29  )
    30  
    31  // Is this an exported - upper case - name?
    32  func isExported(name string) bool {
    33  	rune, _ := utf8.DecodeRuneInString(name)
    34  	return unicode.IsUpper(rune)
    35  }
    36  
    37  // Is this type exported or a builtin?
    38  func isExportedOrBuiltinType(t reflect.Type) bool {
    39  	for t.Kind() == reflect.Ptr {
    40  		t = t.Elem()
    41  	}
    42  	// PkgPath will be non-empty even for an exported type,
    43  	// so we need to check the type name as well.
    44  	return isExported(t.Name()) || t.PkgPath() == ""
    45  }
    46  
    47  var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
    48  
    49  // isContextType returns an indication if the given t is of context.Context or *context.Context type
    50  func isContextType(t reflect.Type) bool {
    51  	for t.Kind() == reflect.Ptr {
    52  		t = t.Elem()
    53  	}
    54  	return t == contextType
    55  }
    56  
    57  var errorType = reflect.TypeOf((*error)(nil)).Elem()
    58  
    59  // Implements this type the error interface
    60  func isErrorType(t reflect.Type) bool {
    61  	for t.Kind() == reflect.Ptr {
    62  		t = t.Elem()
    63  	}
    64  	return t.Implements(errorType)
    65  }
    66  
    67  var subscriptionType = reflect.TypeOf((*Subscription)(nil)).Elem()
    68  
    69  // isSubscriptionType returns an indication if the given t is of Subscription or *Subscription type
    70  func isSubscriptionType(t reflect.Type) bool {
    71  	for t.Kind() == reflect.Ptr {
    72  		t = t.Elem()
    73  	}
    74  	return t == subscriptionType
    75  }
    76  
    77  // isPubSub tests whether the given method has as as first argument a context.Context
    78  // and returns the pair (Subscription, error)
    79  func isPubSub(methodType reflect.Type) bool {
    80  	// numIn(0) is the receiver type
    81  	if methodType.NumIn() < 2 || methodType.NumOut() != 2 {
    82  		return false
    83  	}
    84  
    85  	return isContextType(methodType.In(1)) &&
    86  		isSubscriptionType(methodType.Out(0)) &&
    87  		isErrorType(methodType.Out(1))
    88  }
    89  
    90  // formatName will convert to first character to lower case
    91  func formatName(name string) string {
    92  	ret := []rune(name)
    93  	if len(ret) > 0 {
    94  		ret[0] = unicode.ToLower(ret[0])
    95  	}
    96  	return string(ret)
    97  }
    98  
    99  var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem()
   100  
   101  // Indication if this type should be serialized in hex
   102  func isHexNum(t reflect.Type) bool {
   103  	if t == nil {
   104  		return false
   105  	}
   106  	for t.Kind() == reflect.Ptr {
   107  		t = t.Elem()
   108  	}
   109  
   110  	return t == bigIntType
   111  }
   112  
   113  // suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria
   114  // for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server
   115  // documentation for a summary of these criteria.
   116  func suitableCallbacks(rcvr reflect.Value, typ reflect.Type) (callbacks, subscriptions) {
   117  	callbacks := make(callbacks)
   118  	subscriptions := make(subscriptions)
   119  
   120  METHODS:
   121  	for m := 0; m < typ.NumMethod(); m++ {
   122  		method := typ.Method(m)
   123  		mtype := method.Type
   124  		mname := formatName(method.Name)
   125  		if method.PkgPath != "" { // method must be exported
   126  			continue
   127  		}
   128  
   129  		var h callback
   130  		h.isSubscribe = isPubSub(mtype)
   131  		h.rcvr = rcvr
   132  		h.method = method
   133  		h.errPos = -1
   134  
   135  		firstArg := 1
   136  		numIn := mtype.NumIn()
   137  		if numIn >= 2 && mtype.In(1) == contextType {
   138  			h.hasCtx = true
   139  			firstArg = 2
   140  		}
   141  
   142  		if h.isSubscribe {
   143  			h.argTypes = make([]reflect.Type, numIn-firstArg) // skip rcvr type
   144  			for i := firstArg; i < numIn; i++ {
   145  				argType := mtype.In(i)
   146  				if isExportedOrBuiltinType(argType) {
   147  					h.argTypes[i-firstArg] = argType
   148  				} else {
   149  					continue METHODS
   150  				}
   151  			}
   152  
   153  			subscriptions[mname] = &h
   154  			continue METHODS
   155  		}
   156  
   157  		// determine method arguments, ignore first arg since it's the receiver type
   158  		// Arguments must be exported or builtin types
   159  		h.argTypes = make([]reflect.Type, numIn-firstArg)
   160  		for i := firstArg; i < numIn; i++ {
   161  			argType := mtype.In(i)
   162  			if !isExportedOrBuiltinType(argType) {
   163  				continue METHODS
   164  			}
   165  			h.argTypes[i-firstArg] = argType
   166  		}
   167  
   168  		// check that all returned values are exported or builtin types
   169  		for i := 0; i < mtype.NumOut(); i++ {
   170  			if !isExportedOrBuiltinType(mtype.Out(i)) {
   171  				continue METHODS
   172  			}
   173  		}
   174  
   175  		// when a method returns an error it must be the last returned value
   176  		h.errPos = -1
   177  		for i := 0; i < mtype.NumOut(); i++ {
   178  			if isErrorType(mtype.Out(i)) {
   179  				h.errPos = i
   180  				break
   181  			}
   182  		}
   183  
   184  		if h.errPos >= 0 && h.errPos != mtype.NumOut()-1 {
   185  			continue METHODS
   186  		}
   187  
   188  		switch mtype.NumOut() {
   189  		case 0, 1:
   190  			break
   191  		case 2:
   192  			if h.errPos == -1 { // method must one return value and 1 error
   193  				continue METHODS
   194  			}
   195  			break
   196  		default:
   197  			continue METHODS
   198  		}
   199  
   200  		callbacks[mname] = &h
   201  	}
   202  
   203  	return callbacks, subscriptions
   204  }
   205  
   206  func newSubscriptionID() (string, error) {
   207  	var subid [16]byte
   208  	n, _ := rand.Read(subid[:])
   209  	if n != 16 {
   210  		return "", errors.New("Unable to generate subscription id")
   211  	}
   212  	return "0x" + hex.EncodeToString(subid[:]), nil
   213  }
   214  
   215  // SupportedModules returns the collection of API's that the RPC server offers
   216  // on which the given client connects.
   217  func SupportedModules(client Client) (map[string]string, error) {
   218  	req := JSONRequest{
   219  		Id:      []byte("1"),
   220  		Version: "2.0",
   221  		Method:  MetadataApi + "_modules",
   222  	}
   223  	if err := client.Send(req); err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	var response JSONResponse
   228  	if err := client.Recv(&response); err != nil {
   229  		return nil, err
   230  	}
   231  	if response.Result != nil {
   232  		mods := make(map[string]string)
   233  		if modules, ok := response.Result.(map[string]interface{}); ok {
   234  			for m, v := range modules {
   235  				mods[m] = fmt.Sprintf("%s", v)
   236  			}
   237  			return mods, nil
   238  		}
   239  	}
   240  	return nil, fmt.Errorf("unable to retrieve modules")
   241  }