diff --git a/CHANGELOG.md b/CHANGELOG.md index 38294120c..855454050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ FEATURES * [stake] Creation of a validator/delegation generics in `/types` * [stake] Helper Description of the store in x/stake/store.md * [stake] removed use of caches in the stake keeper +* [stake] Added REST API * [Makefile] Added terraform/ansible playbooks to easily create remote testnets on Digital Ocean BUG FIXES diff --git a/client/context/helpers.go b/client/context/helpers.go index f4686befd..f47cc7ff4 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -59,7 +59,7 @@ func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName // Query from Tendermint with the provided storename and path func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { - path := fmt.Sprintf("/store/%s/key", storeName) + path := fmt.Sprintf("/store/%s/%s", storeName, endPath) node, err := ctx.GetNode() if err != nil { return res, err diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 840312ef6..8c6530dec 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -2,6 +2,7 @@ package lcd import ( "bytes" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" cryptoKeys "github.com/tendermint/go-crypto/keys" tmcfg "github.com/tendermint/tendermint/config" nm "github.com/tendermint/tendermint/node" @@ -31,17 +33,21 @@ import ( client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - bapp "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - btypes "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/stake" ) var ( - coinDenom = "mycoin" + coinDenom = "steak" coinAmount = int64(10000000) + validatorAddr1 = "" + validatorAddr2 = "" + // XXX bad globals name = "test" password = "0123456789" @@ -220,6 +226,7 @@ func TestValidators(t *testing.T) { func TestCoinSend(t *testing.T) { // query empty + //res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) @@ -309,6 +316,63 @@ func TestTxs(t *testing.T) { // assert.NotEqual(t, "[]", body) } +func TestValidatorsQuery(t *testing.T) { + validators := getValidators(t) + assert.Equal(t, len(validators), 2) + + // make sure all the validators were found (order unknown because sorted by owner addr) + foundVal1, foundVal2 := false, false + res1, res2 := hex.EncodeToString(validators[0].Owner), hex.EncodeToString(validators[1].Owner) + if res1 == validatorAddr1 || res2 == validatorAddr1 { + foundVal1 = true + } + if res1 == validatorAddr2 || res2 == validatorAddr2 { + foundVal2 = true + } + assert.True(t, foundVal1, "validatorAddr1 %v, res1 %v, res2 %v", validatorAddr1, res1, res2) + assert.True(t, foundVal2, "validatorAddr2 %v, res1 %v, res2 %v", validatorAddr2, res1, res2) +} + +func TestBond(t *testing.T) { + + // create bond TX + resultTx := doBond(t, port, seed) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was commited + assert.Equal(t, uint32(0), resultTx.CheckTx.Code) + assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc := getAccount(t, sendAddr) + coins := acc.GetCoins() + assert.Equal(t, int64(87), coins.AmountOf(coinDenom)) + + // query candidate + bond := getDelegation(t, sendAddr, validatorAddr1) + assert.Equal(t, "10/1", bond.Shares.String()) +} + +func TestUnbond(t *testing.T) { + + // create unbond TX + resultTx := doUnbond(t, port, seed) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was commited + assert.Equal(t, uint32(0), resultTx.CheckTx.Code) + assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc := getAccount(t, sendAddr) + coins := acc.GetCoins() + assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) + + // query candidate + bond := getDelegation(t, sendAddr, validatorAddr1) + assert.Equal(t, "9/1", bond.Shares.String()) +} + //__________________________________________________________ // helpers @@ -324,26 +388,18 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { if err != nil { return nil, nil, err } - var info cryptoKeys.Info - info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed - if err != nil { - return nil, nil, err - } - - pubKey := info.PubKey - sendAddr = pubKey.Address().String() // XXX global config := GetConfig() config.Consensus.TimeoutCommit = 1000 config.Consensus.SkipTimeoutCommit = false logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - // logger = log.NewFilter(logger, log.AllowError()) + logger = log.NewFilter(logger, log.AllowError()) privValidatorFile := config.PrivValidatorFile() privVal := pvm.LoadOrGenFilePV(privValidatorFile) db := dbm.NewMemDB() - app := bapp.NewBasecoinApp(logger, db) - cdc = bapp.MakeCodec() // XXX + app := gapp.NewGaiaApp(logger, db) + cdc = gapp.MakeCodec() // XXX genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) @@ -351,21 +407,53 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { return nil, nil, err } - coins := sdk.Coins{{coinDenom, coinAmount}} - appState := map[string]interface{}{ - "accounts": []*btypes.GenesisAccount{ - { - Name: "tester", - Address: pubKey.Address(), - Coins: coins, - }, + genDoc.Validators = append(genDoc.Validators, + tmtypes.GenesisValidator{ + PubKey: crypto.GenPrivKeyEd25519().PubKey(), + Power: 1, + Name: "val", }, - } - stateBytes, err := json.Marshal(appState) + ) + + pk1 := genDoc.Validators[0].PubKey + pk2 := genDoc.Validators[1].PubKey + validatorAddr1 = hex.EncodeToString(pk1.Address()) + validatorAddr2 = hex.EncodeToString(pk2.Address()) + + // NOTE it's bad practice to reuse pk address for the owner address but doing in the + // test for simplicity + var appGenTxs [2]json.RawMessage + appGenTxs[0], _, _, err = gapp.GaiaAppGenTxNF(cdc, pk1, pk1.Address(), "test_val1", true) if err != nil { return nil, nil, err } - genDoc.AppStateJSON = stateBytes + appGenTxs[1], _, _, err = gapp.GaiaAppGenTxNF(cdc, pk2, pk2.Address(), "test_val2", true) + if err != nil { + return nil, nil, err + } + + genesisState, err := gapp.GaiaAppGenState(cdc, appGenTxs[:]) + if err != nil { + return nil, nil, err + } + + // add the sendAddr to genesis + var info cryptoKeys.Info + info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed + if err != nil { + return nil, nil, err + } + sendAddr = info.PubKey.Address().String() // XXX global + accAuth := auth.NewBaseAccountWithAddress(info.PubKey.Address()) + accAuth.Coins = sdk.Coins{{"steak", 100}} + acc := gapp.NewGenesisAccount(&accAuth) + genesisState.Accounts = append(genesisState.Accounts, acc) + + appState, err := wire.MarshalJSONIndent(cdc, genesisState) + if err != nil { + return nil, nil, err + } + genDoc.AppStateJSON = appState // LCD listen address port = fmt.Sprintf("%d", 17377) // XXX @@ -379,7 +467,7 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { if err != nil { return nil, nil, err } - lcd, err := startLCD(logger, listenAddr) + lcd, err := startLCD(logger, listenAddr, cdc) if err != nil { return nil, nil, err } @@ -418,7 +506,7 @@ func startTM(cfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, p } // start the LCD. note this blocks! -func startLCD(logger log.Logger, listenAddr string) (net.Listener, error) { +func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { handler := createHandler(cdc) return tmrpc.StartHTTPServer(listenAddr, handler, logger) } @@ -494,3 +582,81 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad return resultTx } + +func getDelegation(t *testing.T, delegatorAddr, candidateAddr string) stake.Delegation { + // get the account to get the sequence + res, body := request(t, port, "GET", "/stake/"+delegatorAddr+"/bonding_status/"+candidateAddr, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var bond stake.Delegation + err := cdc.UnmarshalJSON([]byte(body), &bond) + require.Nil(t, err) + return bond +} + +func doBond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, sendAddr) + sequence := acc.GetSequence() + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "sequence": %d, + "delegate": [ + { + "delegator_addr": "%x", + "validator_addr": "%s", + "bond": { "denom": "%s", "amount": 10 } + } + ], + "unbond": [] + }`, name, password, sequence, acc.GetAddress(), validatorAddr1, coinDenom)) + res, body := request(t, port, "POST", "/stake/delegations", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func doUnbond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, sendAddr) + sequence := acc.GetSequence() + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "sequence": %d, + "bond": [], + "unbond": [ + { + "delegator_addr": "%x", + "validator_addr": "%s", + "shares": "1" + } + ] + }`, name, password, sequence, acc.GetAddress(), validatorAddr1)) + res, body := request(t, port, "POST", "/stake/delegations", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func getValidators(t *testing.T) []stake.Validator { + // get the account to get the sequence + res, body := request(t, port, "GET", "/stake/validators", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var validators stake.Validators + err := cdc.UnmarshalJSON([]byte(body), &validators) + require.Nil(t, err) + return validators +} diff --git a/client/lcd/root.go b/client/lcd/root.go index a7be5079b..df8be897c 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -22,6 +22,7 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" + stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) const ( @@ -83,5 +84,6 @@ func createHandler(cdc *wire.Codec) http.Handler { auth.RegisterRoutes(ctx, r, cdc, "acc") bank.RegisterRoutes(ctx, r, cdc, kb) ibc.RegisterRoutes(ctx, r, cdc, kb) + stake.RegisterRoutes(ctx, r, cdc, kb) return r } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 8cf45e84a..de4a696fd 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -74,7 +74,7 @@ func GaiaAppInit() server.AppInit { FlagsAppGenState: fsAppGenState, FlagsAppGenTx: fsAppGenTx, AppGenTx: GaiaAppGenTx, - AppGenState: GaiaAppGenState, + AppGenState: GaiaAppGenStateJSON, } } @@ -85,19 +85,31 @@ type GaiaGenTx struct { PubKey crypto.PubKey `json:"pub_key"` } -// Generate a gaia genesis transaction +// Generate a gaia genesis transaction with flags func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - - var addr sdk.Address - var secret string clientRoot := viper.GetString(flagClientHome) overwrite := viper.GetBool(flagOWK) name := viper.GetString(flagName) + var addr sdk.Address + var secret string addr, secret, err = server.GenerateSaveCoinKey(clientRoot, name, "1234567890", overwrite) if err != nil { return } + mm := map[string]string{"secret": secret} + var bz []byte + bz, err = cdc.MarshalJSON(mm) + if err != nil { + return + } + cliPrint = json.RawMessage(bz) + return GaiaAppGenTxNF(cdc, pk, addr, name, overwrite) +} + +// Generate a gaia genesis transaction without flags +func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name string, overwrite bool) ( + appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { var bz []byte gaiaGenTx := GaiaGenTx{ @@ -111,13 +123,6 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( } appGenTx = json.RawMessage(bz) - mm := map[string]string{"secret": secret} - bz, err = cdc.MarshalJSON(mm) - if err != nil { - return - } - cliPrint = json.RawMessage(bz) - validator = tmtypes.GenesisValidator{ PubKey: pk, Power: freeFermionVal, @@ -127,7 +132,7 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey -func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { +func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { if len(appGenTxs) == 0 { err = errors.New("must provide at least genesis transaction") @@ -171,10 +176,21 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso } // create the final app state - genesisState := GenesisState{ + genesisState = GenesisState{ Accounts: genaccs, StakeData: stakeData, } + return +} + +// GaiaAppGenState but with JSON +func GaiaAppGenStateJSON(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { + + // create the final app state + genesisState, err := GaiaAppGenState(cdc, appGenTxs) + if err != nil { + return nil, err + } appState, err = wire.MarshalJSONIndent(cdc, genesisState) return } diff --git a/types/tx_msg.go b/types/tx_msg.go index 186cf9b24..240db3106 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -35,7 +35,7 @@ type Tx interface { //__________________________________________________________ -// TxDeocder unmarshals transaction bytes +// TxDecoder unmarshals transaction bytes type TxDecoder func(txBytes []byte) (Tx, Error) //__________________________________________________________ diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 491ad1a7b..84ce9886a 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -72,25 +72,26 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { return err } - // parse out the candidates - var candidates []stake.Validator + // parse out the validators + var validators []stake.Validator for _, KV := range resKVs { var validator stake.Validator cdc.MustUnmarshalBinary(KV.Value, &validator) - candidates = append(candidates, validator) + validators = append(validators, validator) } + switch viper.Get(cli.OutputFlag) { case "text": - for _, candidate := range candidates { - resp, err := candidate.HumanReadableString() + for _, validator := range validators { + resp, err := validator.HumanReadableString() if err != nil { return err } fmt.Println(resp) } case "json": - output, err := wire.MarshalJSONIndent(cdc, candidates) + output, err := wire.MarshalJSONIndent(cdc, validators) if err != nil { return err } @@ -157,7 +158,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query all the candidates bonded to a delegation +// get the command to query all the validators bonded to a delegation func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", @@ -176,7 +177,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { return err } - // parse out the candidates + // parse out the validators var delegations []stake.Delegation for _, KV := range resKVs { var delegation stake.Delegation diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 609390293..3e439c2b4 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,14 +13,21 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -// RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - r.HandleFunc("/stake/{delegator}/bonding_status/{validator}", BondingStatusHandlerFn("stake", cdc, kb, ctx)).Methods("GET") +func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + r.HandleFunc( + "/stake/{delegator}/bonding_status/{validator}", + bondingStatusHandlerFn(ctx, "stake", cdc), + ).Methods("GET") + r.HandleFunc( + "/stake/validators", + validatorsHandlerFn(ctx, "stake", cdc), + ).Methods("GET") } -// BondingStatusHandlerFn - http request handler to query delegator bonding status -func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { +// http request handler to query delegator bonding status +func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + // read parameters vars := mux.Vars(r) delegator := vars["delegator"] @@ -76,3 +82,43 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, w.Write(output) } } + +// http request handler to query list of validators +func validatorsHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + kvs, err := ctx.QuerySubspace(cdc, stake.ValidatorsKey, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't query validators. Error: %s", err.Error()))) + return + } + + // the query will return empty if there are no validators + if len(kvs) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + // parse out the validators + validators := make([]stake.Validator, len(kvs)) + for i, kv := range kvs { + var validator stake.Validator + err = cdc.UnmarshalBinary(kv.Value, &validator) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validators[i] = validator + } + + output, err := cdc.MarshalJSON(validators) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go new file mode 100644 index 000000000..1f3a2957d --- /dev/null +++ b/x/stake/client/rest/rest.go @@ -0,0 +1,15 @@ +package rest + +import ( + "github.com/gorilla/mux" + "github.com/tendermint/go-crypto/keys" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/wire" +) + +// RegisterRoutes registers staking-related REST handlers to a router +func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + registerQueryRoutes(ctx, r, cdc) + registerTxRoutes(ctx, r, cdc, kb) +} diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go new file mode 100644 index 000000000..eaf206bf6 --- /dev/null +++ b/x/stake/client/rest/tx.go @@ -0,0 +1,120 @@ +package rest + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/tendermint/go-crypto/keys" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc( + "/stake/delegations", + editDelegationsRequestHandlerFn(cdc, kb, ctx), + ).Methods("POST") +} + +type editDelegationsBody struct { + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + Sequence int64 `json:"sequence"` + Delegate []stake.MsgDelegate `json:"delegate"` + Unbond []stake.MsgUnbond `json:"unbond"` +} + +func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var m editDelegationsBody + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + err = json.Unmarshal(body, &m) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + // build messages + messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond)) + i := 0 + for _, msg := range m.Delegate { + if !bytes.Equal(info.Address(), msg.DelegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + messages[i] = msg + i++ + } + for _, msg := range m.Unbond { + if !bytes.Equal(info.Address(), msg.DelegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + messages[i] = msg + i++ + } + + // sign messages + signedTxs := make([][]byte, len(messages[:])) + for i, msg := range messages { + // increment sequence for each message + ctx = ctx.WithSequence(m.Sequence) + m.Sequence++ + + txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + signedTxs[i] = txBytes + } + + // send + // XXX the operation might not be atomic if a tx fails + // should we have a sdk.MultiMsg type to make sending atomic? + results := make([]*ctypes.ResultBroadcastTxCommit, len(signedTxs[:])) + for i, txBytes := range signedTxs { + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + results[i] = res + } + + output, err := json.MarshalIndent(results[:], "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index d45adc3d7..be8d0dbe4 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -22,8 +22,8 @@ func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []D // get raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - Pool: initialPool(), - Params: defaultParams(), + Pool: InitialPool(), + Params: DefaultParams(), } } diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index f28a2cf68..01d4434e8 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -586,7 +586,7 @@ func TestGetTendermintUpdatesInserted(t *testing.T) { func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - params := defaultParams() + params := DefaultParams() params.MaxValidators = 2 keeper.setParams(ctx, params) @@ -721,7 +721,7 @@ func TestBond(t *testing.T) { func TestParams(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - expParams := defaultParams() + expParams := DefaultParams() //check that the empty keeper loads the default resParams := keeper.GetParams(ctx) @@ -736,7 +736,7 @@ func TestParams(t *testing.T) { func TestPool(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - expPool := initialPool() + expPool := InitialPool() //check that the empty keeper loads the default resPool := keeper.GetPool(ctx) diff --git a/x/stake/msg.go b/x/stake/msg.go index 41220acc6..1cbf31c82 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -121,8 +121,8 @@ func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { // MsgDelegate - struct for bonding transactions type MsgDelegate struct { - DelegatorAddr sdk.Address `json:"address"` - ValidatorAddr sdk.Address `json:"address"` + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` Bond sdk.Coin `json:"bond"` } @@ -170,8 +170,8 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { // MsgUnbond - struct for unbonding transactions type MsgUnbond struct { - DelegatorAddr sdk.Address `json:"address"` - ValidatorAddr sdk.Address `json:"address"` + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` Shares string `json:"shares"` } diff --git a/x/stake/params.go b/x/stake/params.go index 32b8c0ae8..ace39935c 100644 --- a/x/stake/params.go +++ b/x/stake/params.go @@ -23,7 +23,8 @@ func (p Params) equal(p2 Params) bool { return bytes.Equal(bz1, bz2) } -func defaultParams() Params { +// default params +func DefaultParams() Params { return Params{ InflationRateChange: sdk.NewRat(13, 100), InflationMax: sdk.NewRat(20, 100), diff --git a/x/stake/pool.go b/x/stake/pool.go index e2547b050..0b320432e 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -31,7 +31,7 @@ func (p Pool) equal(p2 Pool) bool { } // initial pool for testing -func initialPool() Pool { +func InitialPool() Pool { return Pool{ LooseUnbondedTokens: 0, BondedTokens: 0, diff --git a/x/stake/test_common.go b/x/stake/test_common.go index bf3e66551..9f52ad8de 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -113,8 +113,8 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context ) ck := bank.NewKeeper(accountMapper) keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace) - keeper.setPool(ctx, initialPool()) - keeper.setNewParams(ctx, defaultParams()) + keeper.setPool(ctx, InitialPool()) + keeper.setNewParams(ctx, DefaultParams()) // fill all the addresses with some coins for _, addr := range addrs { diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 4f0f6dc06..438b678f1 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -61,7 +61,7 @@ func TestGetInflation(t *testing.T) { func TestProcessProvisions(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - params := defaultParams() + params := DefaultParams() params.MaxValidators = 2 keeper.setParams(ctx, params) pool := keeper.GetPool(ctx) diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go index 1ca5ba2f7..db6ab6f4c 100644 --- a/x/stake/validator_test.go +++ b/x/stake/validator_test.go @@ -169,7 +169,7 @@ func randomValidator(r *rand.Rand) Validator { // generate a random staking state func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) { - pool := initialPool() + pool := InitialPool() validators := make([]Validator, numValidators) for i := 0; i < numValidators; i++ {