Browse Source

rpc: fixes for better type handlings, explicit error field in response, more tests

pull/43/merge
Ethan Buchman 10 years ago
parent
commit
6e81e8a848
6 changed files with 197 additions and 44 deletions
  1. +12
    -0
      account/priv_account.go
  2. +0
    -1
      rpc/core/txs.go
  3. +33
    -9
      rpc/http_handlers.go
  4. +1
    -1
      rpc/http_params.go
  5. +10
    -4
      rpc/http_server.go
  6. +141
    -29
      rpc/test/rpc_test.go

+ 12
- 0
account/priv_account.go View File

@ -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))
}


+ 0
- 1
rpc/core/txs.go View File

@ -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)


+ 33
- 9
rpc/http_handlers.go View File

@ -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() {


+ 1
- 1
rpc/http_params.go View File

@ -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 {


+ 10
- 4
rpc/http_server.go View File

@ -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)


+ 141
- 29
rpc/test/rpc_test.go View File

@ -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*/

Loading…
Cancel
Save