rpc ethereum client protocol RPC integration

This commit is contained in:
Bas van Kervel 2015-09-29 10:50:00 +02:00
parent 27528ad3d2
commit 98d3b24637
6 changed files with 1106 additions and 0 deletions

75
rpc/ecp/doc.go Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2015 The go-ethereum Authors
// 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 ecp implements the Ethereum Client Protocol (ECP) and contains a server to
create a RPC server offering a public API using ECP.
The purpose of this package is to provide access to exported methods an I/O connection.
A client can instantiate a server and register objects. The server will expose the
object as a service. After registration exported methods can be called remotely.
Methods that satisfy the following criteria will be exposed:
- the object is exported
- the method is exported
- arguments must be builtin types or *big.Int
- when the method returns an error its must be the last returned values
The REdis Serialization Protocol (http://redis.io/topics/protocol) is used as
serialization format. It is a fast and easy to parse protocol which is binary safe.
Currently the following types are supported:
- string, send as simple string
- []byte, send as bulk/binary string
- big.Int, send as bulk/binary string in big endian format
- int64, send as integer
- error, as the default error type starting with an error code following the error message
- struct, fields are send in an array in the definition order
- array, as an array
Nil values are send as a null array in case the type is an array, otherwise as a null
binary string.
See the official REdis Serialization Protocol specification page for a description and
examples of how data is serialized. The ecp server uses the same convention as the Redis
server for messages. Requests and responses are represented in an array. Services who return
no data return with the +OK\r\n message as an indication that the request was processed.
Example server:
type ExampleService struct {
}
func (s *ExampleService) Echo(str string, i int) (string, int) {
return str, i
}
func main() {
srvr := ecp.NewServer()
srvr.RegisterName("example", new(ExampleService))
l, _ := net.ListenUnix("unix", &net.UnixAddr{Name: "/tmp/example.sock", Net: "unix"})
go srvr.Serve(l)
...
srvr.Stop()
}
A client can now send the following request:
*3\r\n+example.Echo\r\n+some string\r\n:42\r\n
And receive the following response:
*2\r\n+some string\r\n:42\r\n
*/
package ecp

119
rpc/ecp/errors.go Normal file
View file

@ -0,0 +1,119 @@
package ecp
import (
"fmt"
"reflect"
)
type errorCode int
const (
_ errorCode = iota
ProtocolError // Received message invalid
InvalidRequestError // Received message isn't a valid request
MethodNotFoundError // Requested method not found
InvalidArguments // Received arguments are invalid
LogicError // Service returned an error
)
// isRecoverable returns an indication if the server can recover from the error
func isRecoverable(err error) bool {
switch err.(type) {
case *unknownServiceError, *unknownMethodError, *invalidNumberOfArgumentsError, *invalidStructArgumentError,
*invalidArgumentError, *invalidRequestError, *unsupportedTypeError, *callbackError:
return true
}
return false
}
// InvalidLeadInError is raised when the received message doesn't for a valid
// RESP message. It is a bit more detailed than InvalidRequestError.
type invalidLeadInError struct {
got byte
}
func (e *invalidLeadInError) Error() string {
return fmt.Sprintf("%d ECP error, received unexpected byte 0x%x", ProtocolError, e.got)
}
// UnexpectedByteError occurs when incoming data doesn't follow the protocol specifications
type unexpectedByteError struct {
expected, got byte
}
func (e *unexpectedByteError) Error() string {
return fmt.Sprintf("%d ECP error, expected 0x%x got 0x%x", ProtocolError, e.expected, e.got)
}
type unknownServiceError struct {
svc string
}
func (e *unknownServiceError) Error() string {
return fmt.Sprintf("%d service '%s' not found", MethodNotFoundError, e.svc)
}
type unknownMethodError struct {
svc string
method string
}
func (e *unknownMethodError) Error() string {
return fmt.Sprintf("%d method '%s.%s' not found", MethodNotFoundError, e.svc, e.method)
}
type invalidNumberOfArgumentsError struct {
svc string
method string
exp int
got int
}
func (e *invalidNumberOfArgumentsError) Error() string {
return fmt.Sprintf("%d %s.%s expect %d argument(s) but got %d",
InvalidArguments, e.svc, e.method, e.exp, e.got)
}
type invalidArgumentError struct {
pos int
exp reflect.Type
got reflect.Type
}
func (e *invalidArgumentError) Error() string {
return fmt.Sprintf("%d expected type for argument %d is %s but got %s", InvalidArguments, e.pos, e.exp, e.got)
}
type invalidStructArgumentError struct {
to reflect.Type
exp int
got int
}
func (e *invalidStructArgumentError) Error() string {
return fmt.Sprintf("%s expected %d values but got %d", e.to.Name(), e.exp, e.got)
}
type invalidRequestError struct {
}
func (e *invalidRequestError) Error() string {
return fmt.Sprintf("%d invalid request", InvalidRequestError)
}
type unsupportedTypeError struct {
name string
}
func (e *unsupportedTypeError) Error() string {
return fmt.Sprintf("%d '%s' is an unsupported type", ProtocolError, e.name)
}
type callbackError struct {
msg string
}
func (e *callbackError) Error() string {
return fmt.Sprintf("%d %s", LogicError, e.msg)
}

380
rpc/ecp/protocol.go Normal file
View file

@ -0,0 +1,380 @@
// Copyright 2015 The go-ethereum Authors
// 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 ecp
import (
"bufio"
"fmt"
"io"
"math/big"
"reflect"
"strconv"
"strings"
)
const (
firstDelim = '\r'
lastDelim = '\n'
delim = "\r\n"
simpleStrMark = '+'
errMark = '-'
intMark = ':'
bulkStrMark = '$'
arrayMark = '*'
methodSep = "."
successReply = "+OK\r\n"
)
type ValueType int
const (
simpleStrValue ValueType = 1 << iota
integerValue
binaryValue
arrayValue
errorValue
nullValue
)
// ServerCodec implements reading and writing RPC messages for the server side of a RPC session.
type ServerCodec interface {
// Read the next request
Read() (*request, error)
// WriteResponse writes an array of values as a response to the client
WriteResponse([]interface{}) error
// WriteError writes an error to the client
WriteError(error) error
// Close the underlying data stream(s)
Close() error
}
// ECPCodec implements the REdis Server Protocol (RESP).
// See for more information http://redis.io/topics/protocol.
type ECPCodec struct {
rc io.ReadCloser
r *bufio.Reader
wc io.WriteCloser
w *bufio.Writer
}
// Create a new Ethereum Client Protocol Codec. It reads incoming requests from the supplied
// reader and writes responses to the supplied writer.
func NewECPCodec(r io.ReadCloser, w io.WriteCloser) ServerCodec {
return &ECPCodec{r, bufio.NewReader(r), w, bufio.NewWriter(w)}
}
type message struct {
Kind ValueType
Val interface{}
}
// Close underlying reader and writer
func (c *ECPCodec) Close() error {
if err := c.w.Flush(); err != nil {
return err
}
c.rc.Close()
c.wc.Close()
return nil
}
// Read reads a new RPC request.
func (c *ECPCodec) Read() (*request, error) {
msg, err := c.read()
if err != nil {
return nil, err
}
if msg.Kind == arrayValue {
if values, ok := msg.Val.([]*message); ok {
if len(values) > 0 {
req := new(request)
method := strings.Split(fmt.Sprintf("%s", values[0].Val), methodSep)
if len(method) != 2 {
return nil, &invalidRequestError{}
}
req.service, req.method = method[0], method[1]
req.args = make([]interface{}, len(values)-1)
for i := 1; i < len(values); i++ {
req.args[i-1] = values[i].Val
}
return req, nil
}
}
}
return nil, &invalidRequestError{}
}
func (c *ECPCodec) read() (*message, error) {
mark, err := c.r.ReadByte()
if err != nil {
return nil, err
}
switch mark {
case simpleStrMark:
return c.readSimpleStr()
case bulkStrMark:
return c.readBulkStr()
case intMark:
return c.readInteger()
case arrayMark:
return c.readArray()
case errMark:
return c.readError()
}
return nil, &invalidLeadInError{mark}
}
func (c *ECPCodec) readSimpleStr() (*message, error) {
str, err := c.r.ReadBytes(firstDelim)
if err != nil {
return nil, err
}
if nl, err := c.r.ReadByte(); err != nil {
return nil, err
} else if nl != lastDelim {
return nil, &unexpectedByteError{lastDelim, nl}
}
return &message{simpleStrValue, string(str[:len(str)-1])}, nil
}
func (c *ECPCodec) readError() (*message, error) {
e, err := c.r.ReadBytes(firstDelim)
if err != nil {
return nil, err
}
if nl, err := c.r.ReadByte(); err != nil {
return nil, err
} else if nl != lastDelim {
return nil, &unexpectedByteError{lastDelim, nl}
}
return &message{errorValue, string(e[:len(e)-1])}, nil
}
func (c *ECPCodec) readInteger() (*message, error) {
i, err := c.r.ReadBytes(firstDelim)
if err != nil {
return nil, err
}
if nl, err := c.r.ReadByte(); err != nil {
return nil, err
} else if nl != lastDelim {
return nil, &unexpectedByteError{lastDelim, nl}
}
val, err := strconv.ParseInt(string(i[:len(i)-1]), 10, 64)
if err != nil {
return nil, err
}
return &message{integerValue, val}, nil
}
func (c *ECPCodec) readBulkStr() (*message, error) {
n, err := c.r.ReadBytes(firstDelim)
if err != nil {
return nil, err
}
nl, err := c.r.ReadByte()
if err != nil {
return nil, err
} else if nl != lastDelim {
return nil, &unexpectedByteError{lastDelim, nl}
}
cnt, err := strconv.ParseInt(string(n[:len(n)-1]), 10, 64)
if err != nil {
return nil, err
}
if cnt == -1 {
return &message{nullValue, nil}, nil
}
if cnt < 0 {
return nil, &invalidRequestError{}
}
val := make([]byte, cnt)
tot := int64(0)
for tot < cnt {
n, err := c.r.Read(val[tot:])
if err != nil {
return nil, err
}
tot += int64(n)
}
if cr, err := c.r.ReadByte(); err != nil {
return nil, err
} else if cr != firstDelim {
return nil, &unexpectedByteError{lastDelim, cr}
}
if nl, err := c.r.ReadByte(); err != nil {
return nil, err
} else if nl != lastDelim {
return nil, &unexpectedByteError{lastDelim, nl}
}
return &message{binaryValue, val}, nil
}
func (c *ECPCodec) readArray() (*message, error) {
n, err := c.r.ReadBytes(firstDelim)
if err != nil {
return nil, err
}
if nl, err := c.r.ReadByte(); err != nil {
return nil, err
} else if nl != lastDelim {
return nil, &unexpectedByteError{lastDelim, nl}
}
nElem, err := strconv.ParseInt(string(n[:len(n)-1]), 10, 64)
if err != nil {
return nil, err
}
arr := make([]*message, nElem)
for i := int64(0); i < nElem; i++ {
if arr[i], err = c.read(); err != nil {
return nil, err
}
}
return &message{arrayValue, arr}, nil
}
// WriteResponse writes a values to the client. When values is nil the default
// success response is written.
func (c *ECPCodec) WriteResponse(values []interface{}) error {
defer c.w.Flush()
if values == nil {
_, e := c.w.WriteString(successReply)
return e
}
return c.writeArray(values)
}
// WriteError writes the supplied error to the client
func (c *ECPCodec) WriteError(err error) error {
defer c.w.Flush()
_, e := c.w.WriteString(fmt.Sprintf("%c%s%s", errMark, err, delim))
return e
}
func (c *ECPCodec) writeSimpleString(str string) error {
_, err := c.w.WriteString(fmt.Sprintf("%c%s%s", simpleStrMark, str, delim))
return err
}
func (c *ECPCodec) writeInteger(i int64) error {
_, err := c.w.WriteString(fmt.Sprintf("%c%d%s", intMark, i, delim))
return err
}
func (c *ECPCodec) writeBinary(bin []byte) error {
if bin == nil {
_, err := c.w.WriteString(fmt.Sprintf("%c-1%s", bulkStrMark, delim))
return err
}
if _, err := c.w.WriteString(fmt.Sprintf("%c%d%s", bulkStrMark, len(bin), delim)); err != nil {
return err
}
if _, err := c.w.Write(bin); err != nil {
return err
}
_, err := c.w.WriteString(delim)
return err
}
func (c *ECPCodec) writeStruct(obj interface{}) error {
val := reflect.ValueOf(obj)
fields := make([]interface{}, val.NumField())
cnt := 0
for i := 0; i < val.NumField(); i++ {
if val.Field(i).CanInterface() { // skip unexported fields
fields[cnt] = val.Field(i).Interface()
cnt++
}
}
return c.writeArray(fields[:cnt])
}
func (c *ECPCodec) writeArray(arr []interface{}) (err error) {
if arr == nil {
_, err = c.w.WriteString(fmt.Sprintf("%c-1%s", arrayMark, delim))
return
}
// array length prefix
if _, err = c.w.WriteString(fmt.Sprintf("%c%d%s", arrayMark, len(arr), delim)); err != nil {
return
}
for i := 0; i < len(arr) && err == nil; i++ {
switch val := arr[i].(type) {
case string:
err = c.writeSimpleString(val)
case *string:
err = c.writeSimpleString(*val)
case []byte:
err = c.writeBinary(val)
case big.Int:
err = c.writeBinary(val.Bytes())
case *big.Int:
err = c.writeBinary(val.Bytes())
case int64:
err = c.writeInteger(val)
case nil: // use nil bulk string to represent nil values
err = c.writeBinary(nil)
case error:
err = c.WriteError(val)
default:
typ := reflect.TypeOf(val)
if typ.Kind() == reflect.Struct {
err = c.writeStruct(val)
} else if a, ok := val.([]interface{}); ok {
err = c.writeArray(a)
} else {
err = &unsupportedTypeError{typ.Name()}
}
}
}
return
}

107
rpc/ecp/protocol_test.go Normal file
View file

@ -0,0 +1,107 @@
package ecp
import (
"bytes"
"fmt"
"io"
"math/big"
"strings"
"testing"
)
type testReader struct {
r io.Reader
}
func (r *testReader) Read(p []byte) (n int, err error) {
return r.r.Read(p)
}
func (r *testReader) Close() error {
return nil
}
func newTestReader(source string) io.ReadCloser {
return &testReader{strings.NewReader(source)}
}
func TestNoArgsAndRets(t *testing.T) {
r := newTestReader("*1\r\n+Service.Method\r\n")
codec := NewECPCodec(r, nil)
req, err := codec.Read()
if err != nil {
t.Fatalf("%s\n", err)
}
if req.service != "Service" {
t.Fatalf("Expected service 'Service' but got '%s'\n", req.service)
}
if req.method != "Method" {
t.Fatalf("Expected method 'Method' but got '%s'\n", req.method)
}
}
func TestWithArgs(t *testing.T) {
i := big.NewInt(123456789)
ib := i.Bytes()
str := "some string"
bstr := []byte{'a', '\r', '\n', 'b'}
in := int64(42)
r := newTestReader(fmt.Sprintf("*5\r\n+Service.Method\r\n$%d\r\n%s\r\n+%s\r\n$%d\r\n%s\r\n:%d\r\n",
len(ib), ib, str, len(bstr), bstr, in))
codec := NewECPCodec(r, nil)
req, err := codec.Read()
if err != nil {
t.Fatalf("%s\n", err)
}
if req.service != "Service" {
t.Fatalf("Expected service 'Service' but got '%s'\n", req.service)
}
if req.method != "Method" {
t.Fatalf("Expected method 'Method' but got '%s'\n", req.method)
}
if len(req.args) != 4 {
t.Fatalf("Expected 3 args, got %d args(s)\n", len(req.args))
}
if pin, ok := req.args[0].([]byte); ok {
parsedInt := new(big.Int).SetBytes(pin)
if parsedInt.Cmp(i) != 0 {
t.Fatalf("Expected big.Int(%d).Cmp(%d) == 0\n", parsedInt, i)
}
} else {
t.Fatalf("Expected arg[0] to be []byte, got %T\n", req.args[0])
}
if s, ok := req.args[1].(string); ok {
if str != s {
t.Fatalf("Expected 'some string' == '%s'\n", s)
}
} else {
t.Fatalf("Expected arg[1] to be string, got %T\n", req.args[1])
}
if arr, ok := req.args[2].([]byte); ok {
if bytes.Compare(arr, bstr) != 0 {
t.Fatalf("Expected %s, got %s\n", bstr, arr)
}
} else {
t.Fatalf("Expected arg[2] to be []byte, got %T\n", req.args[2])
}
if val, ok := req.args[3].(int64); ok {
if in != val {
t.Fatalf("Expected %d, got %d\n", in, val)
}
} else {
t.Fatalf("Expected arg[3] to be int64, got %T\n", req.args[3])
}
}

258
rpc/ecp/server.go Normal file
View file

@ -0,0 +1,258 @@
// Copyright 2015 The go-ethereum Authors
// 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 ecp
import (
"fmt"
"net"
"reflect"
"sync"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
)
type request struct {
service string // registered service name
method string // callback on service
args []interface{} // arguments for callback
}
type callback struct {
method reflect.Method // callback
args []reflect.Type // input argument types
errPos int // err return idx, of -1 when method cannot return error
}
type service struct {
name string // name for service
rcvr reflect.Value // receiver of methods for the service
typ reflect.Type // receiver type
callbacks callbacks // registered handlers
}
type serviceRegistry map[string]*service
type codecCollection map[ServerCodec]interface{}
type callbacks map[string]*callback
// Server represents a RPC server
type Server struct {
listener net.Listener
serviceMap serviceRegistry
mu sync.Mutex // protects codec collection
codecs codecCollection // active connections/codecs
}
// NewServer will create a new server instance with no registered handlers.
func NewServer() *Server {
return &Server{
serviceMap: make(serviceRegistry),
codecs: make(codecCollection),
}
}
// Register publishes suitable methods of rcvr. Methods will be published
// with a dot between the rcrv and method, e.g. ChainManager.GetBlock.
func (s *Server) Register(rcvr interface{}) error {
return s.register(rcvr, "", false)
}
// Register publishes suitable methods of rcvr. Methods will be published
// with a dot between the given name and method, e.g. ChainManager.GetBlock.
func (s *Server) RegisterName(name string, rcvr interface{}) error {
return s.register(rcvr, name, true)
}
func (s *Server) register(rcvr interface{}, name string, useName bool) error {
if s.serviceMap == nil {
s.serviceMap = make(map[string]*service)
}
svc := new(service)
svc.typ = reflect.TypeOf(rcvr)
svc.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(svc.rcvr).Type().Name()
if useName {
sname = name
}
if sname == "" {
return fmt.Errorf("no service name for type %s", svc.typ.String())
}
if !isExported(sname) && !useName {
return fmt.Errorf("%s is not exported", sname)
}
if _, present := s.serviceMap[sname]; present {
return fmt.Errorf("%s already registered", sname)
}
svc.name = sname
svc.callbacks = suitableCallbacks(svc.typ)
if len(svc.callbacks) == 0 {
return fmt.Errorf("Service doesn't have any suitable methods to expose")
}
s.serviceMap[svc.name] = svc
return nil
}
// Serve accepts connections and starts processing incoming requests.
func (s *Server) Serve(l net.Listener) {
s.listener = l
for {
c, err := s.listener.Accept()
if err != nil {
glog.V(logger.Debug).Infof("%v\n", err)
break
}
glog.V(logger.Debug).Infof("Accepted connection from %s\n", c.RemoteAddr())
codec := NewECPCodec(c, c)
s.mu.Lock()
s.codecs[codec] = nil
s.mu.Unlock()
go s.serveCodec(codec)
}
// stopped accepting new connections, close existing
for c, _ := range s.codecs {
s.close(c)
}
}
// Stop will stop listening for new connection and closes existing connections
func (s *Server) Stop() error {
if s.listener != nil {
return s.listener.Close()
}
return nil
}
// serveCodec start the server and uses the supplied codec to read and write
// requests and response. It will call Close on the codec when it returns.
func (s *Server) serveCodec(codec ServerCodec) error {
defer s.close(codec)
for {
svc, c, argv, err := s.readRequest(codec)
if err != nil {
err2 := codec.WriteError(err)
if !isRecoverable(err) {
glog.V(logger.Error).Infoln(err)
return err
}
if !isRecoverable(err2) {
glog.V(logger.Error).Infoln(err2)
return err2
}
continue
}
if res, err := s.call(c, svc.rcvr, argv); err != nil {
if err = codec.WriteError(err); err != nil {
if !isRecoverable(err) {
glog.V(logger.Error).Infoln(err)
return err
}
}
} else {
if res == nil { // callback has no return values
if err = codec.WriteResponse(nil); err != nil {
if !isRecoverable(err) {
glog.V(logger.Error).Infoln(err)
return err
}
}
} else {
values := make([]interface{}, len(res))
for i := 0; i < len(res); i++ {
values[i] = res[i].Interface()
}
if err = codec.WriteResponse(values); err != nil {
if !isRecoverable(err) {
glog.V(logger.Error).Infoln(err)
return err
}
}
}
}
}
}
func (s *Server) close(codec ServerCodec) {
codec.Close()
s.mu.Lock()
delete(s.codecs, codec)
s.mu.Unlock()
}
func (s *Server) call(c *callback, rcvr reflect.Value, argv []reflect.Value) ([]reflect.Value, error) {
args := make([]reflect.Value, 1+len(argv))
args[0] = rcvr
for i := 0; i < len(argv); i++ {
args[i+1] = argv[i]
}
rets := c.method.Func.Call(args)
// check for error
if c.errPos >= 0 && !rets[c.errPos].IsNil() {
return nil, &callbackError{fmt.Sprintf("%s", rets[c.errPos])}
}
// don't send error when there is none
if c.errPos > 0 {
return rets[:c.errPos], nil
}
return rets, nil
}
func (s *Server) readRequest(codec ServerCodec) (svc *service, c *callback, argv []reflect.Value, err error) {
req, err := codec.Read()
if err != nil {
return nil, nil, nil, err
}
// determine callback
if svc = s.serviceMap[req.service]; svc == nil {
return nil, nil, nil, &unknownServiceError{req.service}
}
if c = svc.callbacks[req.method]; c == nil {
return nil, nil, nil, &unknownMethodError{req.service, req.method}
}
// decode params
nArgs := len(c.args)
if nArgs != len(req.args) {
return nil, nil, nil, &invalidNumberOfArgumentsError{req.service, req.method, nArgs, len(req.args)}
}
argv = make([]reflect.Value, nArgs)
for i := 0; i < nArgs; i++ {
argv[i], err = convert(i, req.args[i], c.args[i])
if err != nil {
return nil, nil, nil, err
}
}
return
}

167
rpc/ecp/utils.go Normal file
View file

@ -0,0 +1,167 @@
// Copyright 2015 The go-ethereum Authors
// 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 ecp
import (
"math/big"
"reflect"
"strconv"
"unicode"
"unicode/utf8"
)
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// Implements this type the error interface
func isErrorType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Implements(errorType)
}
func suitableCallbacks(typ reflect.Type) callbacks {
callbacks := make(callbacks)
METHODS:
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
mtype := method.Type
mname := method.Name
if method.PkgPath != "" { // method must be exported
continue
}
var h callback
h.method = method
numIn := mtype.NumIn()
// determine arguments, ignore first arg since it's the receiver type
h.args = make([]reflect.Type, numIn-1)
for i := 1; i < numIn; i++ {
argType := mtype.In(i)
if !isExportedOrBuiltinType(argType) {
continue METHODS
}
h.args[i-1] = argType
}
// determine if callback can return an error
h.errPos = -1
for i := 0; i < mtype.NumOut(); i++ {
if isErrorType(mtype.Out(i)) {
h.errPos = i
break
}
}
// only support methods which return an error as the last returned value (or no error).
if h.errPos == -1 || (h.errPos >= 0 && h.errPos == mtype.NumOut()-1) {
callbacks[mname] = &h
}
}
return callbacks
}
var bigIntType = reflect.TypeOf(new(big.Int))
func isBigInt(typ reflect.Type) bool {
return typ == bigIntType
}
// in the future we might consider adding support for more conversion as the API gets defined
func convert(pos int, val interface{}, to reflect.Type) (reflect.Value, error) {
from := reflect.TypeOf(val)
if from.Kind() == to.Kind() {
return reflect.ValueOf(val), nil
}
plainType := to
for plainType.Kind() == reflect.Ptr {
plainType = plainType.Elem()
}
if plainType.Kind() == reflect.Struct {
return convertStruct(pos, val, to, plainType)
}
retPtr := reflect.New(plainType)
if from.ConvertibleTo(plainType) {
ret := reflect.Indirect(retPtr)
ret.Set(reflect.ValueOf(val).Convert(plainType))
if to.Kind() == reflect.Ptr {
return retPtr, nil
}
return ret, nil
}
if bytes, ok := val.([]byte); ok && to.Kind() == reflect.Int64 {
if i, err := strconv.ParseInt(string(bytes), 10, 64); err == nil {
return reflect.ValueOf(i), nil
}
}
if bytes, ok := val.([]byte); ok && isBigInt(to) {
v := new(big.Int)
v.SetBytes(bytes)
return reflect.ValueOf(v), nil
}
return reflect.Zero(to), &invalidArgumentError{1 + pos, to, reflect.TypeOf(val)}
}
func convertStruct(pos int, val interface{}, to, plainType reflect.Type) (reflect.Value, error) {
if fields, ok := val.([]*message); ok {
if len(fields) != plainType.NumField() {
return reflect.Zero(to), &invalidStructArgumentError{to, plainType.NumField(), len(fields)}
}
ptrVal := reflect.New(plainType)
v := reflect.Indirect(ptrVal)
for i := 0; i < v.NumField(); i++ {
val, err := convert(pos, fields[i].Val, v.Field(i).Type())
if err != nil {
return reflect.Zero(to), err
}
v.Field(i).Set(val)
}
if to.Kind() == reflect.Ptr {
return ptrVal, nil
}
return v, nil
}
return reflect.Zero(to), &invalidArgumentError{1 + pos, to, reflect.TypeOf(val)}
}