From 6e81e8a848dffc99d323d7c2a5f54808447b0ff9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 27 Mar 2015 13:15:59 -0700 Subject: [PATCH] rpc: fixes for better type handlings, explicit error field in response, more tests --- account/priv_account.go | 12 +++ rpc/core/txs.go | 1 - rpc/http_handlers.go | 42 +++++++--- rpc/http_params.go | 2 +- rpc/http_server.go | 14 +++- rpc/test/rpc_test.go | 170 +++++++++++++++++++++++++++++++++------- 6 files changed, 197 insertions(+), 44 deletions(-) diff --git a/account/priv_account.go b/account/priv_account.go index 48e1b75c7..23114893a 100644 --- a/account/priv_account.go +++ b/account/priv_account.go @@ -25,6 +25,18 @@ func GenPrivAccount() *PrivAccount { } } +func GenPrivAccountFromKey(privKeyBytes [64]byte) *PrivAccount { + pubKeyBytes := ed25519.MakePublicKey(&privKeyBytes) + pubKey := PubKeyEd25519(pubKeyBytes[:]) + privKey := PrivKeyEd25519(privKeyBytes[:]) + return &PrivAccount{ + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + } + +} + func (privAccount *PrivAccount) Sign(o Signable) Signature { return privAccount.PrivKey.Sign(SignBytes(o)) } diff --git a/rpc/core/txs.go b/rpc/core/txs.go index 805414898..670984394 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -16,7 +16,6 @@ func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) } } - switch tx.(type) { case *types.SendTx: sendTx := tx.(*types.SendTx) diff --git a/rpc/http_handlers.go b/rpc/http_handlers.go index f1a278c0d..7d9576f70 100644 --- a/rpc/http_handlers.go +++ b/rpc/http_handlers.go @@ -39,6 +39,7 @@ var funcMap = map[string]*FuncWrapper{ "sign_tx": funcWrap("sign_tx", core.SignTx), } +// map each function to an empty struct which can hold its return values var responseMap = map[string]reflect.Value{ "status": reflect.ValueOf(&ResponseStatus{}), "net_info": reflect.ValueOf(&ResponseNetInfo{}), @@ -52,6 +53,7 @@ var responseMap = map[string]reflect.Value{ "sign_tx": reflect.ValueOf(&ResponseSignTx{}), } +// holds all type information for each function type FuncWrapper struct { f reflect.Value // function from "rpc/core" args []reflect.Type // type of each function arg @@ -76,39 +78,57 @@ func toHandler(funcName string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { values, err := queryToValues(funcInfo, r) if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, err.Error()) + 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, err.Error()) + WriteAPIResponse(w, API_ERROR, nil, err.Error()) + return } - WriteAPIResponse(w, API_OK, response) + 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 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)) - fmt.Println("names:", argNames) for i, name := range argNames { ty := argTypes[i] v := reflect.New(ty).Elem() kind := v.Kind() arg := GetParam(r, name) switch kind { + case reflect.Interface: + v = reflect.New(ty) + binary.ReadJSON(v.Interface(), []byte(arg), &err) + if err != nil { + return nil, err + } + v = v.Elem() case reflect.Struct: binary.ReadJSON(v.Interface(), []byte(arg), &err) if err != nil { return nil, err } case reflect.Slice: - v = reflect.ValueOf([]byte(arg)) + rt := ty.Elem() + if rt.Kind() == reflect.Uint8 { + v = reflect.ValueOf([]byte(arg)) + } else { + v = reflect.New(ty) + binary.ReadJSON(v.Interface(), []byte(arg), &err) + if err != nil { + return nil, err + } + v = v.Elem() + } case reflect.Int64: u, err := strconv.ParseInt(arg, 10, 64) if err != nil { @@ -142,6 +162,7 @@ func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, err } // covert a list of interfaces to properly typed values +// TODO! func paramsToValues(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) { values := make([]reflect.Value, len(params)) for i, p := range params { @@ -156,7 +177,9 @@ func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interfac finalType := returnTypes[len(returnTypes)-1] if finalType.Implements(reflect.TypeOf((*error)(nil)).Elem()) { errV := returns[len(returnTypes)-1] - return nil, errV.Interface().(error) + if errV.Interface() != nil { + return nil, fmt.Errorf("%v", errV.Interface()) + } } v := funcInfo.response.Elem() @@ -181,15 +204,16 @@ func JsonRpcHandler(w http.ResponseWriter, r *http.Request) { funcInfo := funcMap[jrpc.Method] values, err := paramsToValues(funcInfo, jrpc.Params) if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, err.Error()) + 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, err.Error()) + WriteAPIResponse(w, API_ERROR, nil, err.Error()) + return } - WriteAPIResponse(w, API_OK, response) + WriteAPIResponse(w, API_OK, response, "") } func initHandlers() { diff --git a/rpc/http_params.go b/rpc/http_params.go index d1d3c0f60..1837fd762 100644 --- a/rpc/http_params.go +++ b/rpc/http_params.go @@ -24,7 +24,7 @@ var ( ) func panicAPI(err error) { - panic(APIResponse{API_INVALID_PARAM, err.Error()}) + panic(APIResponse{API_INVALID_PARAM, nil, err.Error()}) } func GetParam(r *http.Request, param string) string { diff --git a/rpc/http_server.go b/rpc/http_server.go index 17d157fb9..5428d8bb0 100644 --- a/rpc/http_server.go +++ b/rpc/http_server.go @@ -38,16 +38,22 @@ const ( type APIResponse struct { Status APIStatus `json:"status"` Data interface{} `json:"data"` + Error string `json:"error"` } -func (res APIResponse) Error() string { - return fmt.Sprintf("Status(%v) %v", res.Status, res.Data) +func (res APIResponse) StatusError() string { + return fmt.Sprintf("Status(%v) %v", res.Status, res.Error) } -func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}) { +func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}, responseErr string) { res := APIResponse{} res.Status = status + if data == nil { + // so json doesn't vommit + data = struct{}{} + } res.Data = data + res.Error = responseErr buf, n, err := new(bytes.Buffer), new(int64), new(error) binary.WriteJSON(res, buf, n, err) @@ -109,7 +115,7 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler { // If APIResponse, if res, ok := e.(APIResponse); ok { - WriteAPIResponse(rww, res.Status, res.Data) + WriteAPIResponse(rww, res.Status, nil, res.Error) } else { // For the rest, rww.WriteHeader(http.StatusInternalServerError) diff --git a/rpc/test/rpc_test.go b/rpc/test/rpc_test.go index f63e457fc..aa8090ece 100644 --- a/rpc/test/rpc_test.go +++ b/rpc/test/rpc_test.go @@ -5,11 +5,14 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/binary" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/daemon" + "github.com/tendermint/tendermint/logger" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc" + "github.com/tendermint/tendermint/types" "io/ioutil" "net/http" "net/url" @@ -22,6 +25,9 @@ var ( chainId string node *daemon.Node userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB" + userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" + + userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" ) func newNode(ready chan struct{}) { @@ -49,7 +55,9 @@ func init() { app.Set("RPC.HTTP.ListenAddr", rpcAddr) app.Set("GenesisFile", rootDir+"/genesis.json") app.Set("PrivValidatorFile", rootDir+"/priv_validator.json") + app.Set("Log.Stdout.Level", "debug") config.SetApp(app) + logger.InitLog() // start a node ready := make(chan struct{}) go newNode(ready) @@ -105,10 +113,9 @@ func TestGenPriv(t *testing.T) { } } -func TestGetAccount(t *testing.T) { - byteAddr, _ := hex.DecodeString(userAddr) +func getAccount(t *testing.T, addr []byte) *account.Account { resp, err := http.PostForm(requestAddr+"get_account", - url.Values{"address": {string(byteAddr)}}) + url.Values{"address": {string(addr)}}) if err != nil { t.Fatal(err) } @@ -120,54 +127,159 @@ func TestGetAccount(t *testing.T) { var status struct { Status string Data rpc.ResponseGetAccount + Error string } fmt.Println(string(body)) binary.ReadJSON(&status, body, &err) if err != nil { t.Fatal(err) } - if bytes.Compare(status.Data.Account.Address, byteAddr) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", status.Data.Account.Address, byteAddr) - } - + return status.Data.Account } -func TestSignedTx(t *testing.T) { +func TestGetAccount(t *testing.T) { + byteAddr, _ := hex.DecodeString(userAddr) + acc := getAccount(t, byteAddr) + if bytes.Compare(acc.Address, byteAddr) != 0 { + t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr) + } } -/* - acc := mint.MempoolReactor.Mempool.GetState().GetAccount(mint.priv.Address) +func makeTx(t *testing.T, from, to []byte, amt uint64) *types.SendTx { + acc := getAccount(t, from) nonce := 0 if acc != nil { nonce = int(acc.Sequence) + 1 } - - amtInt, err := strconv.Atoi(amt) + bytePub, err := hex.DecodeString(userPub) if err != nil { - return "", err + t.Fatal(err) } - amtUint64 := uint64(amtInt) - - tx := &blk.SendTx{ - Inputs: []*blk.TxInput{ - &blk.TxInput{ - Address: mint.priv.Address, - Amount: amtUint64, + tx := &types.SendTx{ + Inputs: []*types.TxInput{ + &types.TxInput{ + Address: from, + Amount: amt, Sequence: uint(nonce), Signature: account.SignatureEd25519{}, - PubKey: mint.priv.PubKey, + PubKey: account.PubKeyEd25519(bytePub), }, }, - Outputs: []*blk.TxOutput{ - &blk.TxOutput{ - Address: addrB, - Amount: amtUint64, + Outputs: []*types.TxOutput{ + &types.TxOutput{ + Address: to, + Amount: amt, }, }, } - tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx)) - err = mint.MempoolReactor.BroadcastTx(tx) - return hex.EncodeToString(merkle.HashFromBinary(tx)), err + return tx +} + +func requestResponse(t *testing.T, method string, values url.Values, status interface{}) { + resp, err := http.PostForm(requestAddr+method, values) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + fmt.Println(string(body)) + binary.ReadJSON(status, body, &err) + if err != nil { + t.Fatal(err) + } +} + +func TestSignedTx(t *testing.T) { + byteAddr, _ := hex.DecodeString(userAddr) + var byteKey [64]byte + oh, _ := hex.DecodeString(userPriv) + copy(byteKey[:], oh) + + amt := uint64(100) + toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54} + tx := makeTx(t, byteAddr, toAddr, amt) + + /*b, err := json.Marshal(rpc.InterfaceType{types.TxTypeSend, tx}) + if err != nil { + t.Fatal(err) + }*/ + + n := new(int64) + var err error + w := new(bytes.Buffer) + binary.WriteJSON(tx, w, n, &err) + if err != nil { + t.Fatal(err) + } + b := w.Bytes() + + privAcc := account.GenPrivAccountFromKey(byteKey) + if bytes.Compare(privAcc.PubKey.Address(), byteAddr) != 0 { + t.Fatal("Faield to generate correct priv acc") + } + w = new(bytes.Buffer) + binary.WriteJSON([]*account.PrivAccount{privAcc}, w, n, &err) + if err != nil { + t.Fatal(err) + } + + var status struct { + Status string + Data rpc.ResponseSignTx + Error string + } + requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &status) + + if status.Status == "ERROR" { + t.Fatal(status.Error) + } + response := status.Data + //tx = status.Data.(rpc.ResponseSignTx).Tx.(*types.SendTx) + tx = response.Tx.(*types.SendTx) + if bytes.Compare(tx.Inputs[0].Address, byteAddr) != 0 { + t.Fatal("Tx input addresses don't match!") + } + + signBytes := account.SignBytes(tx) + in := tx.Inputs[0] //(*types.SendTx).Inputs[0] + + if err := in.ValidateBasic(); err != nil { + t.Fatal(err) + } + fmt.Println(privAcc.PubKey, in.PubKey) + // Check signatures + // acc := getAccount(t, byteAddr) + // NOTE: using the acc here instead of the in fails; its PubKeyNil ... ? + if !in.PubKey.VerifyBytes(signBytes, in.Signature) { + t.Fatal(types.ErrTxInvalidSignature) + } +} + +func TestBroadcastTx(t *testing.T) { + /* + byteAddr, _ := hex.DecodeString(userAddr) + + amt := uint64(100) + toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54} + tx := makeTx(t, byteAddr, toAddr, amt) + + b, err := json.Marshal([]interface{}{types.TxTypeSend, tx}) + if err != nil { + t.Fatal(err) + } + + var status struct { + Status string + Data rpc.ResponseSignTx + } + // TODO ... + */ +} -*/ +/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx)) +err = mint.MempoolReactor.BroadcastTx(tx) +return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/