Merge pull request #902 from cosmos/cwgoes/expose-bonding-unbonding-blocks

Expose last bonding/unbonding block
This commit is contained in:
Rigel 2018-04-23 16:39:17 -04:00 committed by GitHub
commit 16f43fcb87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
@ -145,11 +146,11 @@ func TestBond(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)

79
x/stake/rest/query.go Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,94 @@
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.Height == b2.Height &&
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), 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)
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]))
}