diff --git a/daemon/daemon.go b/daemon/daemon.go index 03a4d26b..6e14b0e2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -150,7 +150,7 @@ func (n *Node) DialSeed() { } } -func (n *Node) StartRpc() { +func (n *Node) StartRPC() { core.SetBlockStore(n.blockStore) core.SetConsensusState(n.consensusState) core.SetMempoolReactor(n.mempoolReactor) @@ -185,7 +185,7 @@ func Daemon() { // Run the RPC server. if config.App().GetString("RPC.HTTP.ListenAddr") != "" { - n.StartRpc() + n.StartRPC() } // Sleep forever and then... diff --git a/rpc/http_handlers.go b/rpc/handlers.go similarity index 56% rename from rpc/http_handlers.go rename to rpc/handlers.go index 10e981dc..47b63e6c 100644 --- a/rpc/http_handlers.go +++ b/rpc/handlers.go @@ -1,5 +1,9 @@ package rpc +/* +TODO: support Call && GetStorage. +*/ + import ( "encoding/json" "fmt" @@ -12,7 +16,6 @@ import ( // cache all type information about each function up front // (func, responseStruct, argNames) -// XXX: response structs are allocated once and reused - will this cause an issue eg. if a field ever not overwritten? var funcMap = map[string]*FuncWrapper{ "status": funcWrap(core.Status, []string{}), "net_info": funcWrap(core.NetInfo, []string{}), @@ -26,6 +29,18 @@ var funcMap = map[string]*FuncWrapper{ "unsafe/sign_tx": funcWrap(core.SignTx, []string{"tx", "privAccounts"}), } +func initHandlers() { + // HTTP endpoints + for funcName, funcInfo := range funcMap { + http.HandleFunc("/"+funcName, toHttpHandler(funcInfo)) + } + + // JSONRPC endpoints + http.HandleFunc("/", JSONRPCHandler) +} + +//------------------------------------- + // holds all type information for each function type FuncWrapper struct { f reflect.Value // function from "rpc/core" @@ -43,170 +58,6 @@ func funcWrap(f interface{}, args []string) *FuncWrapper { } } -// convert from a function name to the http handler -func toHandler(funcName string) func(http.ResponseWriter, *http.Request) { - funcInfo := funcMap[funcName] - return func(w http.ResponseWriter, r *http.Request) { - values, err := queryToValues(funcInfo, r) - if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error()) - return - } - returns := funcInfo.f.Call(values) - response, err := returnsToResponse(funcInfo, returns) - if err != nil { - WriteAPIResponse(w, API_ERROR, nil, err.Error()) - return - } - WriteAPIResponse(w, API_OK, response, "") - } -} - -// convert a (json) string to a given type -func jsonToArg(ty reflect.Type, arg string) (reflect.Value, error) { - var err error - v := reflect.New(ty) - binary.ReadJSON(v.Interface(), []byte(arg), &err) - if err != nil { - return v, err - } - v = v.Elem() - return v, nil -} - -func jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) { - var err error - v := reflect.New(ty) - binary.ReadJSONFromObject(v.Interface(), object, &err) - if err != nil { - return v, err - } - v = v.Elem() - return v, nil -} - -// covert an http query to a list of properly typed values. -// to be properly decoded the arg must be a concrete type from tendermint (if its an interface). -func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, error) { - argTypes := funcInfo.args - argNames := funcInfo.argNames - - var err error - values := make([]reflect.Value, len(argNames)) - for i, name := range argNames { - ty := argTypes[i] - arg := GetParam(r, name) - //fmt.Println("GetParam()", r, name, arg) - values[i], err = jsonToArg(ty, arg) - if err != nil { - return nil, err - } - } - return values, nil -} - -// covert a list of interfaces to properly typed values -func paramsToValues(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) { - values := make([]reflect.Value, len(params)) - for i, p := range params { - ty := funcInfo.args[i] - v, err := jsonObjectToArg(ty, p) - if err != nil { - return nil, err - } - values[i] = v - } - return values, nil -} - -// returns is Response struct and error. If error is not nil, return it -func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) { - errV := returns[1] - if errV.Interface() != nil { - return nil, fmt.Errorf("%v", errV.Interface()) - } - return returns[0].Interface(), nil -} - -/* -// convert a list of values to a populated struct with the correct types -func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) { - returnTypes := funcInfo.returns - finalType := returnTypes[len(returnTypes)-1] - if finalType.Implements(reflect.TypeOf((*error)(nil)).Elem()) { - errV := returns[len(returnTypes)-1] - if errV.Interface() != nil { - return nil, fmt.Errorf("%v", errV.Interface()) - } - } - - // copy the response struct (New returns a pointer so we have to Elem() twice) - v := reflect.New(funcInfo.response.Elem().Type()).Elem() - nFields := v.NumField() - for i := 0; i < nFields; i++ { - field := v.FieldByIndex([]int{i}) - field.Set(returns[i]) - } - - return v.Interface(), nil -}*/ - -// jsonrpc calls grab the given method's function info and runs reflect.Call -func JsonRpcHandler(w http.ResponseWriter, r *http.Request) { - b, _ := ioutil.ReadAll(r.Body) - var jrpc JsonRpc - err := json.Unmarshal(b, &jrpc) - if err != nil { - // TODO - } - - funcInfo := funcMap[jrpc.Method] - values, err := paramsToValues(funcInfo, jrpc.Params) - if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error()) - return - } - returns := funcInfo.f.Call(values) - response, err := returnsToResponse(funcInfo, returns) - if err != nil { - WriteAPIResponse(w, API_ERROR, nil, err.Error()) - return - } - WriteAPIResponse(w, API_OK, response, "") -} - -func initHandlers() { - // HTTP endpoints - // toHandler runs once for each function and caches - // all reflection data - http.HandleFunc("/status", toHandler("status")) - http.HandleFunc("/net_info", toHandler("net_info")) - http.HandleFunc("/blockchain", toHandler("blockchain")) - http.HandleFunc("/get_block", toHandler("get_block")) - http.HandleFunc("/get_account", toHandler("get_account")) - http.HandleFunc("/list_validators", toHandler("list_validators")) - http.HandleFunc("/broadcast_tx", toHandler("broadcast_tx")) - http.HandleFunc("/list_accounts", toHandler("list_accounts")) - http.HandleFunc("/unsafe/gen_priv_account", toHandler("unsafe/gen_priv_account")) - http.HandleFunc("/unsafe/sign_tx", toHandler("unsafe/sign_tx")) - //http.HandleFunc("/call", CallHandler) - //http.HandleFunc("/get_storage", GetStorageHandler) - - // JsonRPC endpoints - http.HandleFunc("/", JsonRpcHandler) - // unsafe JsonRPC endpoints - //http.HandleFunc("/unsafe", UnsafeJsonRpcHandler) - -} - -type JsonRpc struct { - JsonRpc string `json:"jsonrpc"` - Method string `json:"method"` - Params []interface{} `json:"params"` - Id int `json:"id"` -} - -// this will panic if not passed a function func funcArgTypes(f interface{}) []reflect.Type { t := reflect.TypeOf(f) n := t.NumIn() @@ -226,3 +77,126 @@ func funcReturnTypes(f interface{}) []reflect.Type { } return types } + +//----------------------------------------------------------------------------- +// rpc.json + +type JSONRPC struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params []interface{} `json:"params"` + Id int `json:"id"` +} + +// jsonrpc calls grab the given method's function info and runs reflect.Call +func JSONRPCHandler(w http.ResponseWriter, r *http.Request) { + b, _ := ioutil.ReadAll(r.Body) + var jrpc JSONRPC + err := json.Unmarshal(b, &jrpc) + if err != nil { + // TODO + } + + funcInfo := funcMap[jrpc.Method] + args, err := jsonParamsToArgs(funcInfo, jrpc.Params) + if err != nil { + WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error()) + return + } + returns := funcInfo.f.Call(args) + response, err := returnsToResponse(returns) + if err != nil { + WriteAPIResponse(w, API_ERROR, nil, err.Error()) + return + } + WriteAPIResponse(w, API_OK, response, "") +} + +// covert a list of interfaces to properly typed values +func jsonParamsToArgs(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) { + values := make([]reflect.Value, len(params)) + for i, p := range params { + ty := funcInfo.args[i] + v, err := _jsonObjectToArg(ty, p) + if err != nil { + return nil, err + } + values[i] = v + } + return values, nil +} + +func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) { + var err error + v := reflect.New(ty) + binary.ReadJSONFromObject(v.Interface(), object, &err) + if err != nil { + return v, err + } + v = v.Elem() + return v, nil +} + +// rpc.json +//----------------------------------------------------------------------------- +// rpc.http + +// convert from a function name to the http handler +func toHttpHandler(funcInfo *FuncWrapper) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + args, err := httpParamsToArgs(funcInfo, r) + if err != nil { + WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error()) + return + } + returns := funcInfo.f.Call(args) + response, err := returnsToResponse(returns) + if err != nil { + WriteAPIResponse(w, API_ERROR, nil, err.Error()) + return + } + WriteAPIResponse(w, API_OK, response, "") + } +} + +// Covert an http query to a list of properly typed values. +// To be properly decoded the arg must be a concrete type from tendermint (if its an interface). +func httpParamsToArgs(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, error) { + argTypes := funcInfo.args + argNames := funcInfo.argNames + + var err error + values := make([]reflect.Value, len(argNames)) + for i, name := range argNames { + ty := argTypes[i] + arg := GetParam(r, name) + values[i], err = _jsonStringToArg(ty, arg) + if err != nil { + return nil, err + } + } + return values, nil +} + +func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) { + var err error + v := reflect.New(ty) + binary.ReadJSON(v.Interface(), []byte(arg), &err) + if err != nil { + return v, err + } + v = v.Elem() + return v, nil +} + +// rpc.http +//----------------------------------------------------------------------------- + +// returns is Response struct and error. If error is not nil, return it +func returnsToResponse(returns []reflect.Value) (interface{}, error) { + errV := returns[1] + if errV.Interface() != nil { + return nil, fmt.Errorf("%v", errV.Interface()) + } + return returns[0].Interface(), nil +} diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go index e0f15150..f55bddda 100644 --- a/rpc/test/json_rpc_test.go +++ b/rpc/test/json_rpc_test.go @@ -18,8 +18,8 @@ import ( ) func TestJSONStatus(t *testing.T) { - s := rpc.JsonRpc{ - JsonRpc: "2.0", + s := rpc.JSONRPC{ + JSONRPC: "2.0", Method: "status", Params: []interface{}{}, Id: 0, @@ -53,8 +53,8 @@ func TestJSONStatus(t *testing.T) { } func TestJSONGenPriv(t *testing.T) { - s := rpc.JsonRpc{ - JsonRpc: "2.0", + s := rpc.JSONRPC{ + JSONRPC: "2.0", Method: "unsafe/gen_priv_account", Params: []interface{}{}, Id: 0, diff --git a/rpc/test/test.go b/rpc/test/test.go index 37420e5f..e79e4292 100644 --- a/rpc/test/test.go +++ b/rpc/test/test.go @@ -41,7 +41,7 @@ func newNode(ready chan struct{}) { node.Start() // Run the RPC server. - node.StartRpc() + node.StartRPC() ready <- struct{}{} // Sleep forever @@ -72,8 +72,8 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account { var err error switch typ { case "JSONRPC": - s := rpc.JsonRpc{ - JsonRpc: "2.0", + s := rpc.JSONRPC{ + JSONRPC: "2.0", Method: "get_account", Params: []interface{}{hex.EncodeToString(addr)}, Id: 0,