diff --git a/daemon/daemon.go b/daemon/daemon.go index 03a4d26be..6e14b0e29 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 10e981dc1..47b63e6c4 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,38 +58,75 @@ 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, "") +func funcArgTypes(f interface{}) []reflect.Type { + t := reflect.TypeOf(f) + n := t.NumIn() + types := make([]reflect.Type, n) + for i := 0; i < n; i++ { + types[i] = t.In(i) } + return types } -// 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) +func funcReturnTypes(f interface{}) []reflect.Type { + t := reflect.TypeOf(f) + n := t.NumOut() + types := make([]reflect.Type, n) + for i := 0; i < n; i++ { + types[i] = t.Out(i) + } + 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 { - return v, err + // TODO } - v = v.Elem() - return v, nil + + 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) { +func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) { var err error v := reflect.New(ty) binary.ReadJSONFromObject(v.Interface(), object, &err) @@ -85,9 +137,31 @@ func jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) 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) { +// 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 @@ -96,8 +170,7 @@ func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, err for i, name := range argNames { ty := argTypes[i] arg := GetParam(r, name) - //fmt.Println("GetParam()", r, name, arg) - values[i], err = jsonToArg(ty, arg) + values[i], err = _jsonStringToArg(ty, arg) if err != nil { return nil, err } @@ -105,124 +178,25 @@ func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, 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 +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 } - return values, nil + v = v.Elem() + return v, nil } +// rpc.http +//----------------------------------------------------------------------------- + // returns is Response struct and error. If error is not nil, return it -func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) { +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 } - -/* -// 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() - types := make([]reflect.Type, n) - for i := 0; i < n; i++ { - types[i] = t.In(i) - } - return types -} - -func funcReturnTypes(f interface{}) []reflect.Type { - t := reflect.TypeOf(f) - n := t.NumOut() - types := make([]reflect.Type, n) - for i := 0; i < n; i++ { - types[i] = t.Out(i) - } - return types -} diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go index e0f15150f..f55bddda8 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 37420e5f9..e79e42922 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,