Merge pull request #994 from cosmos/matt/stake-rest
Staking REST endpoints
This commit is contained in:
commit
7f18dbf7a6
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ type Tx interface {
|
|||
|
||||
//__________________________________________________________
|
||||
|
||||
// TxDeocder unmarshals transaction bytes
|
||||
// TxDecoder unmarshals transaction bytes
|
||||
type TxDecoder func(txBytes []byte) (Tx, Error)
|
||||
|
||||
//__________________________________________________________
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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++ {
|
||||
|
|
Loading…
Reference in New Issue