From 2672f4a1bbc4a8358dd595b62ccb9c42b5fac12a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 23 Apr 2018 17:30:54 +0200 Subject: [PATCH 1/4] Add delegator bond query API endpoint --- x/stake/rest/query.go | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 x/stake/rest/query.go diff --git a/x/stake/rest/query.go b/x/stake/rest/query.go new file mode 100644 index 000000000..240f68d96 --- /dev/null +++ b/x/stake/rest/query.go @@ -0,0 +1,79 @@ +package rest + +import ( + "encoding/hex" + "fmt" + "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" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/stake" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc("/stake/{delegator}/bonding_status/{candidate}", BondingStatusHandler("stake", cdc, kb)).Methods("GET") +} + +// BondingStatusHandler - http request handler to query delegator bonding status +func BondingStatusHandler(storeName string, cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) { + ctx := context.NewCoreContextFromViper() + return func(w http.ResponseWriter, r *http.Request) { + // read parameters + vars := mux.Vars(r) + delegator := vars["delegator"] + validator := vars["validator"] + + bz, err := hex.DecodeString(delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + delegatorAddr := sdk.Address(bz) + + bz, err = hex.DecodeString(validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + candidateAddr := sdk.Address(bz) + + key := stake.GetDelegatorBondKey(delegatorAddr, candidateAddr, cdc) + + res, err := ctx.Query(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't query bond. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this bond + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + var bond stake.DelegatorBond + err = cdc.UnmarshalBinary(res, &bond) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode bond. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(bond) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} From 91b1ee393ce55550757d546dd3750908553ee37a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 23 Apr 2018 18:17:21 +0200 Subject: [PATCH 2/4] Add ViewSlashKeeper --- x/stake/keeper.go | 2 +- x/stake/view_slash_keeper.go | 29 ++++++++++ x/stake/view_slash_keeper_test.go | 93 +++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 x/stake/view_slash_keeper.go create mode 100644 x/stake/view_slash_keeper_test.go diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 8bfefef84..f1b436314 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -354,7 +354,7 @@ func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) { //_____________________________________________________________________ -// load a delegator bong +// load a delegator bond func (k Keeper) GetDelegatorBond(ctx sdk.Context, delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { diff --git a/x/stake/view_slash_keeper.go b/x/stake/view_slash_keeper.go new file mode 100644 index 000000000..375c7323b --- /dev/null +++ b/x/stake/view_slash_keeper.go @@ -0,0 +1,29 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// keeper to view information & slash validators +// will be used by governance module +type ViewSlashKeeper struct { + keeper Keeper +} + +// NewViewSlashKeeper creates a keeper restricted to +// viewing information & slashing validators +func NewViewSlashKeeper(k Keeper) ViewSlashKeeper { + return ViewSlashKeeper{k} +} + +// load a delegator bond +func (v ViewSlashKeeper) GetDelegatorBond(ctx sdk.Context, + delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { + return v.keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) +} + +// load n delegator bonds +func (v ViewSlashKeeper) GetDelegatorBonds(ctx sdk.Context, + delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { + return v.keeper.GetDelegatorBonds(ctx, delegator, maxRetrieve) +} diff --git a/x/stake/view_slash_keeper_test.go b/x/stake/view_slash_keeper_test.go new file mode 100644 index 000000000..2e0e70d3b --- /dev/null +++ b/x/stake/view_slash_keeper_test.go @@ -0,0 +1,93 @@ +package stake + +import ( + "bytes" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// tests GetDelegatorBond, GetDelegatorBonds +func TestViewSlashBond(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + + //construct the candidates + amts := []int64{9, 8, 7} + var candidates [3]Candidate + for i, amt := range amts { + candidates[i] = Candidate{ + Address: addrVals[i], + PubKey: pks[i], + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), + } + } + + // first add a candidates[0] to delegate too + keeper.setCandidate(ctx, candidates[0]) + + bond1to1 := DelegatorBond{ + DelegatorAddr: addrDels[0], + CandidateAddr: addrVals[0], + Shares: sdk.NewRat(9), + } + + viewSlashKeeper := NewViewSlashKeeper(keeper) + + bondsEqual := func(b1, b2 DelegatorBond) bool { + return bytes.Equal(b1.DelegatorAddr, b2.DelegatorAddr) && + bytes.Equal(b1.CandidateAddr, b2.CandidateAddr) && + b1.Shares == b2.Shares + } + + // check the empty keeper first + _, found := viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + assert.False(t, found) + + // set and retrieve a record + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found := viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, bondsEqual(bond1to1, resBond)) + + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewRat(99) + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found = viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, bondsEqual(bond1to1, resBond)) + + // add some more records + keeper.setCandidate(ctx, candidates[1]) + keeper.setCandidate(ctx, candidates[2]) + bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9)} + bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9)} + bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9)} + bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9)} + bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9)} + keeper.setDelegatorBond(ctx, bond1to2) + keeper.setDelegatorBond(ctx, bond1to3) + keeper.setDelegatorBond(ctx, bond2to1) + keeper.setDelegatorBond(ctx, bond2to2) + keeper.setDelegatorBond(ctx, bond2to3) + + // test all bond retrieve capabilities + resBonds := viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bondsEqual(bond1to1, resBonds[0])) + assert.True(t, bondsEqual(bond1to2, resBonds[1])) + assert.True(t, bondsEqual(bond1to3, resBonds[2])) + resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 3) + require.Equal(t, 3, len(resBonds)) + resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 2) + require.Equal(t, 2, len(resBonds)) + resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[1], 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bondsEqual(bond2to1, resBonds[0])) + assert.True(t, bondsEqual(bond2to2, resBonds[1])) + assert.True(t, bondsEqual(bond2to3, resBonds[2])) + +} From 5b1e2a3786df99ad39d98e52143e418042335456 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 23 Apr 2018 18:32:55 +0200 Subject: [PATCH 3/4] Add Height field to DelegatorBond, update appropriately --- x/stake/handler.go | 5 +++++ x/stake/keeper_test.go | 20 +++++++++++++++----- x/stake/types.go | 1 + x/stake/view_slash_keeper_test.go | 11 ++++++----- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/x/stake/handler.go b/x/stake/handler.go index 84eeca3c4..d9f718fe5 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -158,6 +158,9 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount) bond.Shares = bond.Shares.Add(newShares) + // Update bond height + bond.Height = ctx.BlockHeight() + k.setDelegatorBond(ctx, bond) k.setCandidate(ctx, candidate) k.setPool(ctx, pool) @@ -226,6 +229,8 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { k.removeDelegatorBond(ctx, bond) } else { + // Update bond height + bond.Height = ctx.BlockHeight() k.setDelegatorBond(ctx, bond) } diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index c42f7182b..03431a403 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -122,6 +122,7 @@ func TestBond(t *testing.T) { bondsEqual := func(b1, b2 DelegatorBond) bool { return bytes.Equal(b1.DelegatorAddr, b2.DelegatorAddr) && bytes.Equal(b1.CandidateAddr, b2.CandidateAddr) && + b1.Height == b2.Height && b1.Shares == b2.Shares } @@ -142,14 +143,23 @@ func TestBond(t *testing.T) { assert.True(t, found) assert.True(t, bondsEqual(bond1to1, resBond)) + // test height + ctx = ctx.WithBlockHeight(10) + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found = keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.Equal(t, resBond.Height, 10) + ctx = ctx.WithBlockHeight(0) + keeper.setDelegatorBond(ctx, bond1to1) + // add some more records keeper.setCandidate(ctx, candidates[1]) keeper.setCandidate(ctx, candidates[2]) - bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9)} - bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9)} - bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9)} - bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9)} - bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9)} + bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9), 4} keeper.setDelegatorBond(ctx, bond1to2) keeper.setDelegatorBond(ctx, bond1to3) keeper.setDelegatorBond(ctx, bond2to1) diff --git a/x/stake/types.go b/x/stake/types.go index be48afe3e..8f9e87cbb 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -164,4 +164,5 @@ type DelegatorBond struct { DelegatorAddr sdk.Address `json:"delegator_addr"` CandidateAddr sdk.Address `json:"candidate_addr"` Shares sdk.Rat `json:"shares"` + Height int64 `json:"height"` // Last height bond updated } diff --git a/x/stake/view_slash_keeper_test.go b/x/stake/view_slash_keeper_test.go index 2e0e70d3b..a8b0e1071 100644 --- a/x/stake/view_slash_keeper_test.go +++ b/x/stake/view_slash_keeper_test.go @@ -40,6 +40,7 @@ func TestViewSlashBond(t *testing.T) { bondsEqual := func(b1, b2 DelegatorBond) bool { return bytes.Equal(b1.DelegatorAddr, b2.DelegatorAddr) && bytes.Equal(b1.CandidateAddr, b2.CandidateAddr) && + b1.Height == b2.Height && b1.Shares == b2.Shares } @@ -63,11 +64,11 @@ func TestViewSlashBond(t *testing.T) { // add some more records keeper.setCandidate(ctx, candidates[1]) keeper.setCandidate(ctx, candidates[2]) - bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9)} - bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9)} - bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9)} - bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9)} - bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9)} + bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9), 4} keeper.setDelegatorBond(ctx, bond1to2) keeper.setDelegatorBond(ctx, bond1to3) keeper.setDelegatorBond(ctx, bond2to1) From 86b79b9c842a9062cf5da17ccde5f01e482dd305 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 23 Apr 2018 18:41:36 +0200 Subject: [PATCH 4/4] Fix testcases & update changelog --- CHANGELOG.md | 1 + x/stake/handler_test.go | 5 +++++ x/stake/keeper_test.go | 9 --------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4c141631..4de7af20a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ BREAKING CHANGES * Remove go-wire, use go-amino * Gaia simple-staking bond and unbond functions replaced +* [stake] Delegator bonds now store the height at which they were updated * [store] Add `SubspaceIterator` and `ReverseSubspaceIterator` to `KVStore` interface * [basecoin] NewBasecoinApp takes a `dbm.DB` and uses namespaced DBs for substores * All module keepers now require a codespace, see basecoin or democoin for usage diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 538c664e9..1240e4e6c 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -76,7 +76,10 @@ func TestIncrementsMsgDelegate(t *testing.T) { // just send the same msgbond multiple times msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, bondAmount) + for i := 0; i < 5; i++ { + ctx = ctx.WithBlockHeight(int64(i)) + got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -90,6 +93,8 @@ func TestIncrementsMsgDelegate(t *testing.T) { expLiabilities := int64(i+2) * bondAmount // (1 self delegation) expDelegatorAcc := initBond - expBond + require.Equal(t, bond.Height, int64(i), "Incorrect bond height") + gotBond := bond.Shares.Evaluate() gotLiabilities := candidate.Liabilities.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 03431a403..e3edfbf9e 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -143,15 +143,6 @@ func TestBond(t *testing.T) { assert.True(t, found) assert.True(t, bondsEqual(bond1to1, resBond)) - // test height - ctx = ctx.WithBlockHeight(10) - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found = keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.Equal(t, resBond.Height, 10) - ctx = ctx.WithBlockHeight(0) - keeper.setDelegatorBond(ctx, bond1to1) - // add some more records keeper.setCandidate(ctx, candidates[1]) keeper.setCandidate(ctx, candidates[2])