Merge PR #3243: allow multiple simultaneous redelegations/ubds between same delegator/validator(s) addresses

* remove kv seperation for marshalling

* pending

* cleanup

* cleanup x2

* pending

* working

* minor refactors

* entry structs defined

* uncompiled mechanism written

* add many compile fixes

* code compiles

* fix test compile errors

* test cover passes

* ...

* multiple entries fix

* ...

* more design fix

* working

* fix test cover bug

* Update PENDING.md

* update comment around queue completion for redelegations/ubds

* basic spec updates

* remove ErrConflictingRedelegation

* @cwgoes comments are resolved

* Update x/staking/keeper/slash.go

Co-Authored-By: rigelrozanski <rigel.rozanski@gmail.com>

* address @alexanderbez comments
This commit is contained in:
frog power 4000 2019-01-16 05:35:18 -05:00 committed by GitHub
parent 916ea85630
commit 133934ae37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 752 additions and 424 deletions

View File

@ -27,9 +27,10 @@ BREAKING CHANGES
* [\#3064](https://github.com/cosmos/cosmos-sdk/issues/3064) Sanitize `sdk.Coin` denom. Coins denoms are now case insensitive, i.e. 100fooToken equals to 100FOOTOKEN.
* [\#3195](https://github.com/cosmos/cosmos-sdk/issues/3195) Allows custom configuration for syncable strategy
* [\#3242](https://github.com/cosmos/cosmos-sdk/issues/3242) Fix infinite gas
meter utilization during aborted ante handler executions.
* [\#2222] [x/staking] `/stake` -> `/staking` module rename
meter utilization during aborted ante handler executions.
* [staking] \#2222 `/stake` -> `/staking` module rename
* [staking] \#1402 Redelegation and unbonding-delegation structs changed to include multiple an array of entries
* Tendermint
* \#3279 Upgrade to Tendermint 0.28.0-dev0
@ -83,6 +84,7 @@ IMPROVEMENTS
slashing, and staking modules.
* [\#3093](https://github.com/cosmos/cosmos-sdk/issues/3093) Ante handler does no longer read all accounts in one go when processing signatures as signature
verification may fail before last signature is checked.
* [x/stake] \#1402 Add for multiple simultaneous redelegations or unbonding-delegations within an unbonding period
* Tendermint

View File

@ -467,8 +467,9 @@ func TestBonding(t *testing.T) {
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
unbonding := getUndelegation(t, port, addr, operAddrs[0])
require.Equal(t, int64(30), unbonding.Balance.Amount.Int64())
ubd := getUnbondingDelegation(t, port, addr, operAddrs[0])
require.Len(t, ubd.Entries, 1)
require.Equal(t, int64(30), ubd.Entries[0].Balance.Amount.Int64())
// test redelegation
resultTx = doBeginRedelegation(t, port, name1, pw, addr, operAddrs[0], operAddrs[1], 30, fees)
@ -502,23 +503,28 @@ func TestBonding(t *testing.T) {
redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1])
require.Len(t, redelegation, 1)
require.Equal(t, "30", redelegation[0].Balance.Amount.String())
require.Len(t, redelegation[0].Entries, 1)
require.Equal(t, "30", redelegation[0].Entries[0].Balance.Amount.String())
delegatorUbds := getDelegatorUnbondingDelegations(t, port, addr)
require.Len(t, delegatorUbds, 1)
require.Equal(t, "30", delegatorUbds[0].Balance.Amount.String())
require.Len(t, delegatorUbds[0].Entries, 1)
require.Equal(t, "30", delegatorUbds[0].Entries[0].Balance.Amount.String())
delegatorReds := getRedelegations(t, port, addr, nil, nil)
require.Len(t, delegatorReds, 1)
require.Equal(t, "30", delegatorReds[0].Balance.Amount.String())
require.Len(t, delegatorReds[0].Entries, 1)
require.Equal(t, "30", delegatorReds[0].Entries[0].Balance.Amount.String())
validatorUbds := getValidatorUnbondingDelegations(t, port, operAddrs[0])
require.Len(t, validatorUbds, 1)
require.Equal(t, "30", validatorUbds[0].Balance.Amount.String())
require.Len(t, validatorUbds[0].Entries, 1)
require.Equal(t, "30", validatorUbds[0].Entries[0].Balance.Amount.String())
validatorReds := getRedelegations(t, port, nil, operAddrs[0], nil)
require.Len(t, validatorReds, 1)
require.Equal(t, "30", validatorReds[0].Balance.Amount.String())
require.Len(t, validatorReds[0].Entries, 1)
require.Equal(t, "30", validatorReds[0].Entries[0].Balance.Amount.String())
// TODO Undonding status not currently implemented
// require.Equal(t, sdk.Unbonding, bondedValidators[0].Status)

View File

@ -942,8 +942,13 @@ func getDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, vali
}
// GET /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr} Query all unbonding delegations between a delegator and a validator
func getUndelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) staking.UnbondingDelegation {
res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/unbonding_delegations/%s", delegatorAddr, validatorAddr), nil)
func getUnbondingDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress,
validatorAddr sdk.ValAddress) staking.UnbondingDelegation {
res, body := Request(t, port, "GET",
fmt.Sprintf("/staking/delegators/%s/unbonding_delegations/%s",
delegatorAddr, validatorAddr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var unbond staking.UnbondingDelegation

View File

@ -116,14 +116,18 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
// iterate through redelegations, reset creation height
app.stakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) {
red.CreationHeight = 0
for i := range red.Entries {
red.Entries[i].CreationHeight = 0
}
app.stakingKeeper.SetRedelegation(ctx, red)
return false
})
// iterate through unbonding delegations, reset creation height
app.stakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) {
ubd.CreationHeight = 0
for i := range ubd.Entries {
ubd.Entries[i].CreationHeight = 0
}
app.stakingKeeper.SetUnbondingDelegation(ctx, ubd)
return false
})

View File

@ -303,7 +303,8 @@ func TestGaiaCLICreateValidator(t *testing.T) {
// Get unbonding delegations from the validator
validatorUbds := f.QueryStakingUnbondingDelegationsFrom(barVal)
require.Len(t, validatorUbds, 1)
require.Equal(t, "1", validatorUbds[0].Balance.Amount.String())
require.Len(t, validatorUbds[0].Entries, 1)
require.Equal(t, "1", validatorUbds[0].Entries[0].Balance.Amount.String())
// Query staking parameters
params := f.QueryStakingParameters()

View File

@ -96,9 +96,9 @@ type Description struct {
### Delegation
Delegations are identified by combining `DelegatorAddr` (the address of the delegator)
with the `OperatorAddr` Delegators are indexed in the store as follows:
with the `ValidatorAddr` Delegators are indexed in the store as follows:
- Delegation: ` 0x0A | DelegatorAddr | OperatorAddr -> amino(delegation)`
- Delegation: ` 0x0A | DelegatorAddr | ValidatorAddr -> amino(delegation)`
Atom holders may delegate coins to validators; under this circumstance their
funds are held in a `Delegation` data structure. It is owned by one
@ -107,26 +107,29 @@ the transaction is the owner of the bond.
```golang
type Delegation struct {
Shares sdk.Dec // delegation shares received
Height int64 // last height bond updated
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
Shares sdk.Dec // delegation shares received
}
```
### UnbondingDelegation
Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`, where shares can be reduced if Byzantine behavior is detected.
Shares in a `Delegation` can be unbonded, but they must for some time exist as
an `UnbondingDelegation`, where shares can be reduced if Byzantine behavior is
detected.
`UnbondingDelegation` are indexed in the store as:
- UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | OperatorAddr ->
- UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | ValidatorAddr ->
amino(unbondingDelegation)`
- UnbondingDelegationByValOwner: ` 0x0C | OperatorAddr | DelegatorAddr | OperatorAddr ->
- UnbondingDelegationByValOwner: ` 0x0C | ValidatorAddr | DelegatorAddr | ValidatorAddr ->
nil`
The first map here is used in queries, to lookup all unbonding delegations for
a given delegator, while the second map is used in slashing, to lookup all
unbonding delegations associated with a given validator that need to be
slashed.
The first map here is used in queries, to lookup all unbonding delegations for
a given delegator, while the second map is used in slashing, to lookup all
unbonding delegations associated with a given validator that need to be
slashed.
A UnbondingDelegation object is created every time an unbonding is initiated.
The unbond must be completed with a second transaction provided by the
@ -134,8 +137,16 @@ delegation owner after the unbonding period has passed.
```golang
type UnbondingDelegation struct {
Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding
CompleteTime int64 // unix time to complete redelegation
DelegatorAddr sdk.AccAddress // delegator
ValidatorAddr sdk.ValAddress // validator unbonding from operator addr
Entries []UnbondingDelegationEntry // unbonding delegation entries
}
type UnbondingDelegationEntry struct {
CreationHeight int64 // height which the unbonding took place
CompletionTime time.Time // unix time for unbonding completion
InitialBalance sdk.Coin // atoms initially scheduled to receive at completion
Balance sdk.Coin // atoms to receive at completion
}
```
@ -143,20 +154,20 @@ type UnbondingDelegation struct {
Shares in a `Delegation` can be rebonded to a different validator, but they must
for some time exist as a `Redelegation`, where shares can be reduced if Byzantine
behavior is detected. This is tracked as moving a delegation from a `FromOperatorAddr`
to a `ToOperatorAddr`.
behavior is detected. This is tracked as moving a delegation from a `ValidatorSrcAddr`
to a `ValidatorDstAddr`.
`Redelegation` are indexed in the store as:
- Redelegations: `0x0D | DelegatorAddr | FromOperatorAddr | ToOperatorAddr ->
- Redelegations: `0x0D | DelegatorAddr | ValidatorSrcAddr | ValidatorDstAddr ->
amino(redelegation)`
- RedelegationsBySrc: `0x0E | FromOperatorAddr | ToOperatorAddr |
- RedelegationsBySrc: `0x0E | ValidatorSrcAddr | ValidatorDstAddr |
DelegatorAddr -> nil`
- RedelegationsByDst: `0x0F | ToOperatorAddr | FromOperatorAddr | DelegatorAddr
- RedelegationsByDst: `0x0F | ValidatorDstAddr | ValidatorSrcAddr | DelegatorAddr
-> nil`
The first map here is used for queries, to lookup all redelegations for a given
delegator. The second map is used for slashing based on the `FromOperatorAddr`,
delegator. The second map is used for slashing based on the `ValidatorSrcAddr`,
while the third map is for slashing based on the ToValOwnerAddr.
A redelegation object is created every time a redelegation occurs. The
@ -167,8 +178,18 @@ the original redelegation has been completed.
```golang
type Redelegation struct {
SourceShares sdk.Dec // amount of source shares redelegating
DestinationShares sdk.Dec // amount of destination shares created at redelegation
CompleteTime int64 // unix time to complete redelegation
DelegatorAddr sdk.AccAddress // delegator
ValidatorSrcAddr sdk.ValAddress // validator redelegation source operator addr
ValidatorDstAddr sdk.ValAddress // validator redelegation destination operator addr
Entries []RedelegationEntry // redelegation entries
}
type RedelegationEntry struct {
CreationHeight int64 // height which the redelegation took place
CompletionTime time.Time // unix time for redelegation completion
InitialBalance sdk.Coin // initial balance when redelegation started
Balance sdk.Coin // current balance (current value held in destination validator)
SharesSrc sdk.Dec // amount of source-validator shares removed by redelegation
SharesDst sdk.Dec // amount of destination-validator shares created by redelegation
}
```

View File

@ -2,7 +2,6 @@ package staking
import (
"fmt"
"sort"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
@ -18,9 +17,11 @@ import (
// Returns final validator set after applying all declaration and delegations
func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res []abci.ValidatorUpdate, err error) {
// We need to pretend to be "n blocks before genesis", where "n" is the validator update delay,
// so that e.g. slashing periods are correctly initialized for the validator set
// e.g. with a one-block offset - the first TM block is at height 1, so state updates applied from genesis.json are in block 0.
// We need to pretend to be "n blocks before genesis", where "n" is the
// validator update delay, so that e.g. slashing periods are correctly
// initialized for the validator set e.g. with a one-block offset - the
// first TM block is at height 1, so state updates applied from
// genesis.json are in block 0.
ctx = ctx.WithBlockHeight(1 - types.ValidatorUpdateDelay)
keeper.SetPool(ctx, data.Pool)
@ -46,20 +47,18 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [
keeper.SetDelegation(ctx, delegation)
}
sort.SliceStable(data.UnbondingDelegations[:], func(i, j int) bool {
return data.UnbondingDelegations[i].CreationHeight < data.UnbondingDelegations[j].CreationHeight
})
for _, ubd := range data.UnbondingDelegations {
keeper.SetUnbondingDelegation(ctx, ubd)
keeper.InsertUnbondingQueue(ctx, ubd)
for _, entry := range ubd.Entries {
keeper.InsertUBDQueue(ctx, ubd, entry.CompletionTime)
}
}
sort.SliceStable(data.Redelegations[:], func(i, j int) bool {
return data.Redelegations[i].CreationHeight < data.Redelegations[j].CreationHeight
})
for _, red := range data.Redelegations {
keeper.SetRedelegation(ctx, red)
keeper.InsertRedelegationQueue(ctx, red)
for _, entry := range red.Entries {
keeper.InsertRedelegationQueue(ctx, red, entry.CompletionTime)
}
}
// don't need to run Tendermint updates if we exported

View File

@ -53,7 +53,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, sdk.T
k.UnbondAllMatureValidatorQueue(ctx)
// Remove all mature unbonding delegations from the ubd queue.
matureUnbonds := k.DequeueAllMatureUnbondingQueue(ctx, ctx.BlockHeader().Time)
matureUnbonds := k.DequeueAllMatureUBDQueue(ctx, ctx.BlockHeader().Time)
for _, dvPair := range matureUnbonds {
err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr)
if err != nil {
@ -70,7 +70,8 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, sdk.T
// Remove all mature redelegations from the red queue.
matureRedelegations := k.DequeueAllMatureRedelegationQueue(ctx, ctx.BlockHeader().Time)
for _, dvvTriplet := range matureRedelegations {
err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddr, dvvTriplet.ValidatorSrcAddr, dvvTriplet.ValidatorDstAddr)
err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddr,
dvvTriplet.ValidatorSrcAddr, dvvTriplet.ValidatorDstAddr)
if err != nil {
continue
}
@ -216,34 +217,34 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper)
}
func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result {
ubd, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount)
completionTime, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount)
if err != nil {
return err.Result()
}
finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(ubd.MinTime)
finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(completionTime)
tags := sdk.NewTags(
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.SrcValidator, []byte(msg.ValidatorAddr.String()),
tags.EndTime, []byte(ubd.MinTime.Format(time.RFC3339)),
tags.EndTime, []byte(completionTime.Format(time.RFC3339)),
)
return sdk.Result{Data: finishTime, Tags: tags}
}
func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result {
red, err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr,
completionTime, err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr,
msg.ValidatorDstAddr, msg.SharesAmount)
if err != nil {
return err.Result()
}
finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(red.MinTime)
finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(completionTime)
resTags := sdk.NewTags(
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()),
tags.DstValidator, []byte(msg.ValidatorDstAddr.String()),
tags.EndTime, []byte(red.MinTime.Format(time.RFC3339)),
tags.EndTime, []byte(completionTime.Format(time.RFC3339)),
)
return sdk.Result{Data: finishTime, Tags: resTags}

View File

@ -416,6 +416,7 @@ func TestIncrementsMsgUnbond(t *testing.T) {
msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares)
numUnbonds := 5
for i := 0; i < numUnbonds; i++ {
got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
var finishTime time.Time
@ -423,7 +424,7 @@ func TestIncrementsMsgUnbond(t *testing.T) {
ctx = ctx.WithBlockTime(finishTime)
EndBlocker(ctx, keeper)
//Check that the accounts and the bond account have the appropriate values
// check that the accounts and the bond account have the appropriate values
validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
@ -849,46 +850,205 @@ func TestTransitiveRedelegation(t *testing.T) {
require.True(t, got.IsOK(), "expected no error")
}
func TestConflictingRedelegation(t *testing.T) {
func TestMultipleRedelegationAtSameTime(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr := sdk.ValAddress(keep.Addrs[0])
validatorAddr2 := sdk.ValAddress(keep.Addrs[1])
valAddr := sdk.ValAddress(keep.Addrs[0])
valAddr2 := sdk.ValAddress(keep.Addrs[1])
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 1
params.UnbondingTime = 1 * time.Second
keeper.SetParams(ctx, params)
// create the validators
msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10)
msgCreateValidator = NewTestMsgCreateValidator(valAddr2, keep.PKs[1], 10)
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// end block to bond them
EndBlocker(ctx, keeper)
// begin redelegate
msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDec(5))
// begin a redelegate
selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator)
msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr,
valAddr, valAddr2, sdk.NewDec(5))
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error, %v", got)
// cannot redelegate again while first redelegation still exists
// there should only be one entry in the redelegation object
rd, found := keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2)
require.True(t, found)
require.Len(t, rd.Entries, 1)
// start a second redelegation at this same time as the first
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate)
require.True(t, got.IsOK(), "expected no error, msg: %v", msgBeginRedelegate)
// progress forward in time
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(10 * time.Second))
// now there should be two entries
rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2)
require.True(t, found)
require.Len(t, rd.Entries, 2)
// complete first redelegation
// move forward in time, should complete both redelegations
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(1 * time.Second))
EndBlocker(ctx, keeper)
// now should be able to redelegate again
rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2)
require.False(t, found)
}
func TestMultipleRedelegationAtUniqueTimes(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
valAddr := sdk.ValAddress(keep.Addrs[0])
valAddr2 := sdk.ValAddress(keep.Addrs[1])
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 10 * time.Second
keeper.SetParams(ctx, params)
// create the validators
msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgCreateValidator = NewTestMsgCreateValidator(valAddr2, keep.PKs[1], 10)
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// end block to bond them
EndBlocker(ctx, keeper)
// begin a redelegate
selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator)
msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr,
valAddr, valAddr2, sdk.NewDec(5))
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error")
require.True(t, got.IsOK(), "expected no error, %v", got)
// move forward in time and start a second redelegation
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second))
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error, msg: %v", msgBeginRedelegate)
// now there should be two entries
rd, found := keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2)
require.True(t, found)
require.Len(t, rd.Entries, 2)
// move forward in time, should complete the first redelegation, but not the second
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second))
EndBlocker(ctx, keeper)
rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2)
require.True(t, found)
require.Len(t, rd.Entries, 1)
// move forward in time, should complete the second redelegation
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second))
EndBlocker(ctx, keeper)
rd, found = keeper.GetRedelegation(ctx, selfDelAddr, valAddr, valAddr2)
require.False(t, found)
}
func TestMultipleUnbondingDelegationAtSameTime(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
valAddr := sdk.ValAddress(keep.Addrs[0])
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 1 * time.Second
keeper.SetParams(ctx, params)
// create the validator
msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// end block to bond
EndBlocker(ctx, keeper)
// begin an unbonding delegation
selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator)
msgBeginUnbonding := NewMsgBeginUnbonding(selfDelAddr, valAddr, sdk.NewDec(5))
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected no error, %v", got)
// there should only be one entry in the ubd object
ubd, found := keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// start a second ubd at this same time as the first
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected no error, msg: %v", msgBeginUnbonding)
// now there should be two entries
ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.True(t, found)
require.Len(t, ubd.Entries, 2)
// move forwaubd in time, should complete both ubds
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(1 * time.Second))
EndBlocker(ctx, keeper)
ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.False(t, found)
}
func TestMultipleUnbondingDelegationAtUniqueTimes(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
valAddr := sdk.ValAddress(keep.Addrs[0])
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 10 * time.Second
keeper.SetParams(ctx, params)
// create the validator
msgCreateValidator := NewTestMsgCreateValidator(valAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// end block to bond
EndBlocker(ctx, keeper)
// begin an unbonding delegation
selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator)
msgBeginUnbonding := NewMsgBeginUnbonding(selfDelAddr, valAddr, sdk.NewDec(5))
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected no error, %v", got)
// there should only be one entry in the ubd object
ubd, found := keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// move forwaubd in time and start a second redelegation
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second))
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected no error, msg: %v", msgBeginUnbonding)
// now there should be two entries
ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.True(t, found)
require.Len(t, ubd.Entries, 2)
// move forwaubd in time, should complete the first redelegation, but not the second
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second))
EndBlocker(ctx, keeper)
ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// move forwaubd in time, should complete the second redelegation
ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(5 * time.Second))
EndBlocker(ctx, keeper)
ubd, found = keeper.GetUnbondingDelegation(ctx, selfDelAddr, valAddr)
require.False(t, found)
}
func TestUnbondingWhenExcessValidators(t *testing.T) {
@ -991,14 +1151,16 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) {
keeper.Slash(ctx, consAddr0, 0, 20, sdk.NewDecWithPrec(5, 1))
// unbonding delegation should have been slashed by half
unbonding, found := keeper.GetUnbondingDelegation(ctx, del, valA)
ubd, found := keeper.GetUnbondingDelegation(ctx, del, valA)
require.True(t, found)
require.Equal(t, int64(2), unbonding.Balance.Amount.Int64())
require.Len(t, ubd.Entries, 1)
require.Equal(t, int64(2), ubd.Entries[0].Balance.Amount.Int64())
// redelegation should have been slashed by half
redelegation, found := keeper.GetRedelegation(ctx, del, valA, valB)
require.True(t, found)
require.Equal(t, int64(3), redelegation.Balance.Amount.Int64())
require.Len(t, redelegation.Entries, 1)
require.Equal(t, int64(3), redelegation.Entries[0].Balance.Amount.Int64())
// destination delegation should have been slashed by half
delegation, found = keeper.GetDelegation(ctx, del, valB)
@ -1015,14 +1177,16 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) {
keeper.Slash(ctx, consAddr0, 2, 10, sdk.NewDecWithPrec(5, 1))
// unbonding delegation should be unchanged
unbonding, found = keeper.GetUnbondingDelegation(ctx, del, valA)
ubd, found = keeper.GetUnbondingDelegation(ctx, del, valA)
require.True(t, found)
require.Equal(t, int64(2), unbonding.Balance.Amount.Int64())
require.Len(t, ubd.Entries, 1)
require.Equal(t, int64(2), ubd.Entries[0].Balance.Amount.Int64())
// redelegation should be unchanged
redelegation, found = keeper.GetRedelegation(ctx, del, valA, valB)
require.True(t, found)
require.Equal(t, int64(3), redelegation.Balance.Amount.Int64())
require.Len(t, redelegation.Entries, 1)
require.Equal(t, int64(3), redelegation.Entries[0].Balance.Amount.Int64())
// destination delegation should be unchanged
delegation, found = keeper.GetDelegation(ctx, del, valB)

View File

@ -170,9 +170,28 @@ func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDe
store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr))
}
// gets a specific unbonding queue timeslice. A timeslice is a slice of DVPairs corresponding to unbonding delegations
// that expire at a certain time.
func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvPairs []types.DVPair) {
// SetUnbondingDelegationEntry adds an entry to the unbonding delegation at
// the given addresses. It creates the unbonding delegation if it does not exist
func (k Keeper) SetUnbondingDelegationEntry(ctx sdk.Context,
delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress,
creationHeight int64, minTime time.Time, balance sdk.Coin) types.UnbondingDelegation {
ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr)
if found {
ubd.AddEntry(creationHeight, minTime, balance)
} else {
ubd = types.NewUnbondingDelegation(delegatorAddr, validatorAddr, creationHeight, minTime, balance)
}
k.SetUnbondingDelegation(ctx, ubd)
return ubd
}
//________________________________________________
// unbonding delegation queue timeslice operations
// gets a specific unbonding queue timeslice. A timeslice is a slice of DVPairs
// corresponding to unbonding delegations that expire at a certain time.
func (k Keeper) GetUBDQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvPairs []types.DVPair) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(GetUnbondingDelegationTimeKey(timestamp))
if bz == nil {
@ -183,38 +202,45 @@ func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time)
}
// Sets a specific unbonding queue timeslice.
func (k Keeper) SetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) {
func (k Keeper) SetUBDQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(keys)
store.Set(GetUnbondingDelegationTimeKey(timestamp), bz)
}
// Insert an unbonding delegation to the appropriate timeslice in the unbonding queue
func (k Keeper) InsertUnbondingQueue(ctx sdk.Context, ubd types.UnbondingDelegation) {
timeSlice := k.GetUnbondingQueueTimeSlice(ctx, ubd.MinTime)
func (k Keeper) InsertUBDQueue(ctx sdk.Context, ubd types.UnbondingDelegation,
completionTime time.Time) {
timeSlice := k.GetUBDQueueTimeSlice(ctx, completionTime)
dvPair := types.DVPair{ubd.DelegatorAddr, ubd.ValidatorAddr}
if len(timeSlice) == 0 {
k.SetUnbondingQueueTimeSlice(ctx, ubd.MinTime, []types.DVPair{dvPair})
k.SetUBDQueueTimeSlice(ctx, completionTime, []types.DVPair{dvPair})
} else {
timeSlice = append(timeSlice, dvPair)
k.SetUnbondingQueueTimeSlice(ctx, ubd.MinTime, timeSlice)
k.SetUBDQueueTimeSlice(ctx, completionTime, timeSlice)
}
}
// Returns all the unbonding queue timeslices from time 0 until endTime
func (k Keeper) UnbondingQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator {
func (k Keeper) UBDQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return store.Iterator(UnbondingQueueKey, sdk.InclusiveEndBytes(GetUnbondingDelegationTimeKey(endTime)))
return store.Iterator(UnbondingQueueKey,
sdk.InclusiveEndBytes(GetUnbondingDelegationTimeKey(endTime)))
}
// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue
func (k Keeper) DequeueAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []types.DVPair) {
// Returns a concatenated list of all the timeslices inclusively previous to
// currTime, and deletes the timeslices from the queue
func (k Keeper) DequeueAllMatureUBDQueue(ctx sdk.Context,
currTime time.Time) (matureUnbonds []types.DVPair) {
store := ctx.KVStore(k.storeKey)
// gets an iterator for all timeslices from time 0 until the current Blockheader time
unbondingTimesliceIterator := k.UnbondingQueueIterator(ctx, ctx.BlockHeader().Time)
unbondingTimesliceIterator := k.UBDQueueIterator(ctx, ctx.BlockHeader().Time)
for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() {
timeslice := []types.DVPair{}
k.cdc.MustUnmarshalBinaryLengthPrefixed(unbondingTimesliceIterator.Value(), &timeslice)
value := unbondingTimesliceIterator.Value()
k.cdc.MustUnmarshalBinaryLengthPrefixed(value, &timeslice)
matureUnbonds = append(matureUnbonds, timeslice...)
store.Delete(unbondingTimesliceIterator.Key())
}
@ -298,6 +324,26 @@ func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) {
store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{})
}
// SetUnbondingDelegationEntry adds an entry to the unbonding delegation at
// the given addresses. It creates the unbonding delegation if it does not exist
func (k Keeper) SetRedelegationEntry(ctx sdk.Context,
delegatorAddr sdk.AccAddress, validatorSrcAddr,
validatorDstAddr sdk.ValAddress, creationHeight int64,
minTime time.Time, balance sdk.Coin,
sharesSrc, sharesDst sdk.Dec) types.Redelegation {
red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr)
if found {
red.AddEntry(creationHeight, minTime, balance, sharesSrc, sharesDst)
} else {
red = types.NewRedelegation(delegatorAddr, validatorSrcAddr,
validatorDstAddr, creationHeight, minTime, balance, sharesSrc,
sharesDst)
}
k.SetRedelegation(ctx, red)
return red
}
// iterate through all redelegations
func (k Keeper) IterateRedelegations(ctx sdk.Context, fn func(index int64, red types.Redelegation) (stop bool)) {
store := ctx.KVStore(k.storeKey)
@ -322,6 +368,9 @@ func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) {
store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr))
}
//________________________________________________
// redelegation queue timeslice operations
// Gets a specific redelegation queue timeslice. A timeslice is a slice of DVVTriplets corresponding to redelegations
// that expire at a certain time.
func (k Keeper) GetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvvTriplets []types.DVVTriplet) {
@ -342,14 +391,16 @@ func (k Keeper) SetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Ti
}
// Insert an redelegation delegation to the appropriate timeslice in the redelegation queue
func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation) {
timeSlice := k.GetRedelegationQueueTimeSlice(ctx, red.MinTime)
func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation,
completionTime time.Time) {
timeSlice := k.GetRedelegationQueueTimeSlice(ctx, completionTime)
dvvTriplet := types.DVVTriplet{red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr}
if len(timeSlice) == 0 {
k.SetRedelegationQueueTimeSlice(ctx, red.MinTime, []types.DVVTriplet{dvvTriplet})
k.SetRedelegationQueueTimeSlice(ctx, completionTime, []types.DVVTriplet{dvvTriplet})
} else {
timeSlice = append(timeSlice, dvvTriplet)
k.SetRedelegationQueueTimeSlice(ctx, red.MinTime, timeSlice)
k.SetRedelegationQueueTimeSlice(ctx, completionTime, timeSlice)
}
}
@ -359,14 +410,16 @@ func (k Keeper) RedelegationQueueIterator(ctx sdk.Context, endTime time.Time) sd
return store.Iterator(RedelegationQueueKey, sdk.InclusiveEndBytes(GetRedelegationTimeKey(endTime)))
}
// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue
// Returns a concatenated list of all the timeslices inclusively previous to
// currTime, and deletes the timeslices from the queue
func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time.Time) (matureRedelegations []types.DVVTriplet) {
store := ctx.KVStore(k.storeKey)
// gets an iterator for all timeslices from time 0 until the current Blockheader time
redelegationTimesliceIterator := k.RedelegationQueueIterator(ctx, ctx.BlockHeader().Time)
for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() {
timeslice := []types.DVVTriplet{}
k.cdc.MustUnmarshalBinaryLengthPrefixed(redelegationTimesliceIterator.Value(), &timeslice)
value := redelegationTimesliceIterator.Value()
k.cdc.MustUnmarshalBinaryLengthPrefixed(value, &timeslice)
matureRedelegations = append(matureRedelegations, timeslice...)
store.Delete(redelegationTimesliceIterator.Key())
}
@ -474,9 +527,9 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA
//______________________________________________________________________________________________________
// get info for begin functions: MinTime and CreationHeight
// get info for begin functions: completionTime and CreationHeight
func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) (
minTime time.Time, height int64, completeNow bool) {
completionTime time.Time, height int64, completeNow bool) {
validator, found := k.GetValidator(ctx, valSrcAddr)
@ -484,17 +537,17 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) (
case !found || validator.Status == sdk.Bonded:
// the longest wait - just unbonding period from now
minTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx))
completionTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx))
height = ctx.BlockHeight()
return minTime, height, false
return completionTime, height, false
case validator.Status == sdk.Unbonded:
return minTime, height, true
return completionTime, height, true
case validator.Status == sdk.Unbonding:
minTime = validator.UnbondingMinTime
completionTime = validator.UnbondingMinTime
height = validator.UnbondingHeight
return minTime, height, false
return completionTime, height, false
default:
panic("unknown validator status")
@ -502,21 +555,15 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) (
}
// begin unbonding an unbonding record
func (k Keeper) BeginUnbonding(ctx sdk.Context,
delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.UnbondingDelegation, sdk.Error) {
// TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402
_, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr)
if found {
return types.UnbondingDelegation{}, types.ErrExistingUnbondingDelegation(k.Codespace())
}
func (k Keeper) BeginUnbonding(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress, sharesAmount sdk.Dec) (completionTime time.Time, sdkErr sdk.Error) {
// create the unbonding delegation
minTime, height, completeNow := k.getBeginInfo(ctx, valAddr)
completionTime, height, completeNow := k.getBeginInfo(ctx, valAddr)
returnAmount, err := k.unbond(ctx, delAddr, valAddr, sharesAmount)
if err != nil {
return types.UnbondingDelegation{}, err
return completionTime, err
}
balance := sdk.NewCoin(k.BondDenom(ctx), returnAmount)
@ -524,107 +571,103 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context,
if completeNow {
_, err := k.bankKeeper.UndelegateCoins(ctx, delAddr, sdk.Coins{balance})
if err != nil {
return types.UnbondingDelegation{}, err
return completionTime, err
}
return types.UnbondingDelegation{MinTime: minTime}, nil
return completionTime, nil
}
ubd := types.UnbondingDelegation{
DelegatorAddr: delAddr,
ValidatorAddr: valAddr,
CreationHeight: height,
MinTime: minTime,
Balance: balance,
InitialBalance: balance,
}
k.SetUnbondingDelegation(ctx, ubd)
k.InsertUnbondingQueue(ctx, ubd)
ubd := k.SetUnbondingDelegationEntry(ctx, delAddr,
valAddr, height, completionTime, balance)
return ubd, nil
k.InsertUBDQueue(ctx, ubd, completionTime)
return completionTime, nil
}
// complete unbonding an unbonding record
// CONTRACT: Expects unbonding passed in has finished the unbonding period
func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Error {
// CompleteUnbonding completes the unbonding of all mature entries in the
// retrieved unbonding delegation object.
func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) sdk.Error {
ubd, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr)
if !found {
return types.ErrNoUnbondingDelegation(k.Codespace())
}
_, _, err := k.bankKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance})
if err != nil {
return err
ctxTime := ctx.BlockHeader().Time
// loop through all the entries and complete unbonding mature entries
for i := 0; i < len(ubd.Entries); i++ {
entry := ubd.Entries[i]
if entry.IsMature(ctxTime) {
ubd.RemoveEntry(int64(i))
i--
_, _, err := k.bankKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{entry.Balance})
if err != nil {
return err
}
}
}
k.RemoveUnbondingDelegation(ctx, ubd)
// set the unbonding delegation or remove it if there are no more entries
if len(ubd.Entries) == 0 {
k.RemoveUnbondingDelegation(ctx, ubd)
} else {
k.SetUnbondingDelegation(ctx, ubd)
}
return nil
}
// begin unbonding / redelegation; create a redelegation record
func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress,
valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) {
valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (
completionTime time.Time, errSdk sdk.Error) {
if bytes.Equal(valSrcAddr, valDstAddr) {
return types.Redelegation{}, types.ErrSelfRedelegation(k.Codespace())
}
// check if there is already a redelgation in progress from src to dst
// TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402
_, found := k.GetRedelegation(ctx, delAddr, valSrcAddr, valDstAddr)
if found {
return types.Redelegation{}, types.ErrConflictingRedelegation(k.Codespace())
return time.Time{}, types.ErrSelfRedelegation(k.Codespace())
}
// check if this is a transitive redelegation
if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) {
return types.Redelegation{}, types.ErrTransitiveRedelegation(k.Codespace())
return time.Time{}, types.ErrTransitiveRedelegation(k.Codespace())
}
returnAmount, err := k.unbond(ctx, delAddr, valSrcAddr, sharesAmount)
if err != nil {
return types.Redelegation{}, err
return time.Time{}, err
}
if returnAmount.IsZero() {
return types.Redelegation{}, types.ErrVerySmallRedelegation(k.Codespace())
return time.Time{}, types.ErrVerySmallRedelegation(k.Codespace())
}
returnCoin := sdk.NewCoin(k.BondDenom(ctx), returnAmount)
dstValidator, found := k.GetValidator(ctx, valDstAddr)
if !found {
return types.Redelegation{}, types.ErrBadRedelegationDst(k.Codespace())
return time.Time{}, types.ErrBadRedelegationDst(k.Codespace())
}
sharesCreated, err := k.Delegate(ctx, delAddr, returnCoin, dstValidator, false)
if err != nil {
return types.Redelegation{}, err
return time.Time{}, err
}
// create the unbonding delegation
minTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr)
completionTime, height, completeNow := k.getBeginInfo(ctx, valSrcAddr)
if completeNow { // no need to create the redelegation object
return types.Redelegation{MinTime: minTime}, nil
return completionTime, nil
}
red := types.Redelegation{
DelegatorAddr: delAddr,
ValidatorSrcAddr: valSrcAddr,
ValidatorDstAddr: valDstAddr,
CreationHeight: height,
MinTime: minTime,
SharesDst: sharesCreated,
SharesSrc: sharesAmount,
Balance: returnCoin,
InitialBalance: returnCoin,
}
k.SetRedelegation(ctx, red)
k.InsertRedelegationQueue(ctx, red)
return red, nil
red := k.SetRedelegationEntry(ctx, delAddr, valSrcAddr, valDstAddr,
height, completionTime, returnCoin, sharesAmount, sharesCreated)
k.InsertRedelegationQueue(ctx, red, completionTime)
return completionTime, nil
}
// complete unbonding an ongoing redelegation
// CompleteRedelegation completes the unbonding of all mature entries in the
// retrieved unbonding delegation object.
func (k Keeper) CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress,
valSrcAddr, valDstAddr sdk.ValAddress) sdk.Error {
@ -633,12 +676,23 @@ func (k Keeper) CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress,
return types.ErrNoRedelegation(k.Codespace())
}
// ensure that enough time has passed
ctxTime := ctx.BlockHeader().Time
if red.MinTime.After(ctxTime) {
return types.ErrNotMature(k.Codespace(), "redelegation", "unit-time", red.MinTime, ctxTime)
// loop through all the entries and complete mature redelegation entries
for i := 0; i < len(red.Entries); i++ {
entry := red.Entries[i]
if entry.IsMature(ctxTime) {
red.RemoveEntry(int64(i))
i--
}
}
// set the redelegation or remove it if there are no more entries
if len(red.Entries) == 0 {
k.RemoveRedelegation(ctx, red)
} else {
k.SetRedelegation(ctx, red)
}
k.RemoveRedelegation(ctx, red)
return nil
}

View File

@ -137,13 +137,8 @@ func TestDelegation(t *testing.T) {
func TestUnbondingDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
ubd := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 0,
MinTime: time.Unix(0, 0),
Balance: sdk.NewInt64Coin(types.DefaultBondDenom, 5),
}
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0,
time.Unix(0, 0), sdk.NewInt64Coin(types.DefaultBondDenom, 5))
// set and retrieve a record
keeper.SetUnbondingDelegation(ctx, ubd)
@ -152,7 +147,7 @@ func TestUnbondingDelegation(t *testing.T) {
require.True(t, ubd.Equal(resUnbond))
// modify a records, save, and retrieve
ubd.Balance = sdk.NewInt64Coin(types.DefaultBondDenom, 21)
ubd.Entries[0].Balance = sdk.NewInt64Coin(types.DefaultBondDenom, 21)
keeper.SetUnbondingDelegation(ctx, ubd)
resUnbonds := keeper.GetUnbondingDelegations(ctx, addrDels[0], 5)
@ -338,9 +333,10 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) {
// retrieve the unbonding delegation
ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6)))
assert.Equal(t, blockHeight, ubd.CreationHeight)
assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime))
require.Len(t, ubd.Entries, 1)
require.True(t, ubd.Entries[0].Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6)))
assert.Equal(t, blockHeight, ubd.Entries[0].CreationHeight)
assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.Entries[0].CompletionTime))
}
func TestUndelegateFromUnbondedValidator(t *testing.T) {
@ -490,15 +486,9 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) {
func TestGetRedelegationsFromValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 0,
MinTime: time.Unix(0, 0),
SharesSrc: sdk.NewDec(5),
SharesDst: sdk.NewDec(5),
}
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0,
time.Unix(0, 0), sdk.NewInt64Coin(types.DefaultBondDenom, 5),
sdk.NewDec(5), sdk.NewDec(5))
// set and retrieve a record
keeper.SetRedelegation(ctx, rd)
@ -520,15 +510,9 @@ func TestGetRedelegationsFromValidator(t *testing.T) {
func TestRedelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 0,
MinTime: time.Unix(0, 0),
SharesSrc: sdk.NewDec(5),
SharesDst: sdk.NewDec(5),
}
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0,
time.Unix(0, 0), sdk.NewInt64Coin(types.DefaultBondDenom, 5),
sdk.NewDec(5), sdk.NewDec(5))
// test shouldn't have and redelegations
has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1])
@ -556,8 +540,8 @@ func TestRedelegation(t *testing.T) {
require.True(t, has)
// modify a records, save, and retrieve
rd.SharesSrc = sdk.NewDec(21)
rd.SharesDst = sdk.NewDec(21)
rd.Entries[0].SharesSrc = sdk.NewDec(21)
rd.Entries[0].SharesDst = sdk.NewDec(21)
keeper.SetRedelegation(ctx, rd)
resRed, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
@ -742,9 +726,10 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) {
// retrieve the unbonding delegation
ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6)))
assert.Equal(t, blockHeight, ubd.CreationHeight)
assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime))
require.Len(t, ubd.Entries, 1)
require.True(t, ubd.Entries[0].Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6)))
assert.Equal(t, blockHeight, ubd.Entries[0].CreationHeight)
assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.Entries[0].CompletionTime))
}
func TestRedelegateFromUnbondedValidator(t *testing.T) {

View File

@ -145,34 +145,41 @@ func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) {
// (the amount actually slashed may be less if there's
// insufficient stake remaining)
func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation types.UnbondingDelegation,
infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Int) {
infractionHeight int64, slashFactor sdk.Dec) (totalSlashAmount sdk.Int) {
now := ctx.BlockHeader().Time
totalSlashAmount = sdk.ZeroInt()
// If unbonding started before this height, stake didn't contribute to infraction
if unbondingDelegation.CreationHeight < infractionHeight {
return sdk.ZeroInt()
}
// perform slashing on all entries within the unbonding delegation
for i, entry := range unbondingDelegation.Entries {
if unbondingDelegation.MinTime.Before(now) {
// Unbonding delegation no longer eligible for slashing, skip it
// TODO Settle and delete it automatically?
return sdk.ZeroInt()
}
// If unbonding started before this height, stake didn't contribute to infraction
if entry.CreationHeight < infractionHeight {
continue
}
// Calculate slash amount proportional to stake contributing to infraction
slashAmountDec := slashFactor.MulInt(unbondingDelegation.InitialBalance.Amount)
slashAmount = slashAmountDec.TruncateInt()
if entry.IsMature(now) {
// Unbonding delegation no longer eligible for slashing, skip it
continue
}
// Don't slash more tokens than held
// Possible since the unbonding delegation may already
// have been slashed, and slash amounts are calculated
// according to stake held at time of infraction
unbondingSlashAmount := sdk.MinInt(slashAmount, unbondingDelegation.Balance.Amount)
// Calculate slash amount proportional to stake contributing to infraction
slashAmountDec := slashFactor.MulInt(entry.InitialBalance.Amount)
slashAmount := slashAmountDec.TruncateInt()
totalSlashAmount = totalSlashAmount.Add(slashAmount)
// Update unbonding delegation if necessary
if !unbondingSlashAmount.IsZero() {
unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount)
// Don't slash more tokens than held
// Possible since the unbonding delegation may already
// have been slashed, and slash amounts are calculated
// according to stake held at time of infraction
unbondingSlashAmount := sdk.MinInt(slashAmount, entry.Balance.Amount)
// Update unbonding delegation if necessary
if unbondingSlashAmount.IsZero() {
continue
}
entry.Balance.Amount = entry.Balance.Amount.Sub(unbondingSlashAmount)
unbondingDelegation.Entries[i] = entry
k.SetUnbondingDelegation(ctx, unbondingDelegation)
pool := k.GetPool(ctx)
@ -182,7 +189,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
k.SetPool(ctx, pool)
}
return slashAmount
return totalSlashAmount
}
// slash a redelegation and update the pool
@ -192,44 +199,51 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
// insufficient stake remaining)
// nolint: unparam
func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, redelegation types.Redelegation,
infractionHeight int64, slashFactor sdk.Dec) (slashAmount sdk.Int) {
infractionHeight int64, slashFactor sdk.Dec) (totalSlashAmount sdk.Int) {
now := ctx.BlockHeader().Time
totalSlashAmount = sdk.ZeroInt()
// If redelegation started before this height, stake didn't contribute to infraction
if redelegation.CreationHeight < infractionHeight {
return sdk.ZeroInt()
}
// perform slashing on all entries within the redelegation
for i, entry := range redelegation.Entries {
if redelegation.MinTime.Before(now) {
// Redelegation no longer eligible for slashing, skip it
// TODO Delete it automatically?
return sdk.ZeroInt()
}
// If redelegation started before this height, stake didn't contribute to infraction
if entry.CreationHeight < infractionHeight {
continue
}
// Calculate slash amount proportional to stake contributing to infraction
slashAmountDec := slashFactor.MulInt(redelegation.InitialBalance.Amount)
slashAmount = slashAmountDec.TruncateInt()
if entry.IsMature(now) {
// Redelegation no longer eligible for slashing, skip it
continue
}
// Don't slash more tokens than held
// Possible since the redelegation may already
// have been slashed, and slash amounts are calculated
// according to stake held at time of infraction
redelegationSlashAmount := sdk.MinInt(slashAmount, redelegation.Balance.Amount)
// Calculate slash amount proportional to stake contributing to infraction
slashAmountDec := slashFactor.MulInt(entry.InitialBalance.Amount)
slashAmount := slashAmountDec.TruncateInt()
totalSlashAmount = totalSlashAmount.Add(slashAmount)
// Update redelegation if necessary
if !redelegationSlashAmount.IsZero() {
redelegation.Balance.Amount = redelegation.Balance.Amount.Sub(redelegationSlashAmount)
k.SetRedelegation(ctx, redelegation)
}
// Don't slash more tokens than held
// Possible since the redelegation may already
// have been slashed, and slash amounts are calculated
// according to stake held at time of infraction
redelegationSlashAmount := sdk.MinInt(slashAmount, entry.Balance.Amount)
// Unbond from target validator
sharesToUnbond := slashFactor.Mul(redelegation.SharesDst)
if !sharesToUnbond.IsZero() {
// Update entry if necessary
if !redelegationSlashAmount.IsZero() {
entry.Balance.Amount = entry.Balance.Amount.Sub(redelegationSlashAmount)
redelegation.Entries[i] = entry
k.SetRedelegation(ctx, redelegation)
}
// Unbond from target validator
sharesToUnbond := slashFactor.Mul(entry.SharesDst)
if sharesToUnbond.IsZero() {
continue
}
delegation, found := k.GetDelegation(ctx, redelegation.DelegatorAddr, redelegation.ValidatorDstAddr)
if !found {
// If deleted, delegation has zero shares, and we can't unbond any more
return slashAmount
continue
}
if sharesToUnbond.GT(delegation.Shares) {
sharesToUnbond = delegation.Shares
@ -246,5 +260,5 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re
k.SetPool(ctx, pool)
}
return slashAmount
return totalSlashAmount
}

View File

@ -70,16 +70,11 @@ func TestSlashUnbondingDelegation(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
fraction := sdk.NewDecWithPrec(5, 1)
// set an unbonding delegation
ubd := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 0,
// expiration timestamp (beyond which the unbonding delegation shouldn't be slashed)
MinTime: time.Unix(0, 0),
InitialBalance: sdk.NewInt64Coin(params.BondDenom, 10),
Balance: sdk.NewInt64Coin(params.BondDenom, 10),
}
// set an unbonding delegation with expiration timestamp (beyond which the
// unbonding delegation shouldn't be slashed)
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 0,
time.Unix(5, 0), sdk.NewInt64Coin(params.BondDenom, 10))
keeper.SetUnbondingDelegation(ctx, ubd)
// unbonding started prior to the infraction height, stakw didn't contribute
@ -100,12 +95,13 @@ func TestSlashUnbondingDelegation(t *testing.T) {
require.Equal(t, int64(5), slashAmount.Int64())
ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// initialbalance unchanged
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.InitialBalance)
// initial balance unchanged
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.Entries[0].InitialBalance)
// balance decreased
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Balance)
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Entries[0].Balance)
newPool := keeper.GetPool(ctx)
require.Equal(t, int64(5), oldPool.LooseTokens.Sub(newPool.LooseTokens).Int64())
}
@ -115,19 +111,12 @@ func TestSlashRedelegation(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
fraction := sdk.NewDecWithPrec(5, 1)
// set a redelegation
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 0,
// expiration timestamp (beyond which the redelegation shouldn't be slashed)
MinTime: time.Unix(0, 0),
SharesSrc: sdk.NewDec(10),
SharesDst: sdk.NewDec(10),
InitialBalance: sdk.NewInt64Coin(params.BondDenom, 10),
Balance: sdk.NewInt64Coin(params.BondDenom, 10),
}
// set a redelegation with an expiration timestamp beyond which the
// redelegation shouldn't be slashed
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0,
time.Unix(5, 0), sdk.NewInt64Coin(params.BondDenom, 10), sdk.NewDec(10),
sdk.NewDec(10))
keeper.SetRedelegation(ctx, rd)
// set the associated delegation
@ -162,16 +151,17 @@ func TestSlashRedelegation(t *testing.T) {
require.Equal(t, int64(5), slashAmount.Int64())
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.Len(t, rd.Entries, 1)
// end block
updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx)
require.Equal(t, 1, len(updates))
// initialbalance unchanged
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.InitialBalance)
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.Entries[0].InitialBalance)
// balance decreased
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Balance)
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Entries[0].Balance)
// shares decreased
del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1])
@ -252,16 +242,10 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
consAddr := sdk.ConsAddress(PKs[0].Address())
fraction := sdk.NewDecWithPrec(5, 1)
// set an unbonding delegation
ubd := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 11,
// expiration timestamp (beyond which the unbonding delegation shouldn't be slashed)
MinTime: time.Unix(0, 0),
InitialBalance: sdk.NewInt64Coin(params.BondDenom, 4),
Balance: sdk.NewInt64Coin(params.BondDenom, 4),
}
// set an unbonding delegation with expiration timestamp beyond which the
// unbonding delegation shouldn't be slashed
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11,
time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 4))
keeper.SetUnbondingDelegation(ctx, ubd)
// slash validator for the first time
@ -278,8 +262,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
// read updating unbonding delegation
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// balance decreased
require.Equal(t, sdk.NewInt(2), ubd.Balance.Amount)
require.Equal(t, sdk.NewInt(2), ubd.Entries[0].Balance.Amount)
// read updated pool
newPool := keeper.GetPool(ctx)
// bonded tokens burned
@ -298,8 +283,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
keeper.Slash(ctx, consAddr, 9, 10, fraction)
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// balance decreased again
require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount)
require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// bonded tokens burned again
@ -318,8 +304,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
keeper.Slash(ctx, consAddr, 9, 10, fraction)
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// balance unchanged
require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount)
require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// bonded tokens burned again
@ -338,8 +325,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
keeper.Slash(ctx, consAddr, 9, 10, fraction)
ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.Len(t, ubd.Entries, 1)
// balance unchanged
require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount)
require.Equal(t, sdk.NewInt(0), ubd.Entries[0].Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// just 1 bonded token burned again since that's all the validator now has
@ -360,17 +348,9 @@ func TestSlashWithRedelegation(t *testing.T) {
fraction := sdk.NewDecWithPrec(5, 1)
// set a redelegation
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 11,
MinTime: time.Unix(0, 0),
SharesSrc: sdk.NewDec(6),
SharesDst: sdk.NewDec(6),
InitialBalance: sdk.NewInt64Coin(params.BondDenom, 6),
Balance: sdk.NewInt64Coin(params.BondDenom, 6),
}
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11,
time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 6), sdk.NewDec(6),
sdk.NewDec(6))
keeper.SetRedelegation(ctx, rd)
// set the associated delegation
@ -396,8 +376,9 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.Len(t, rd.Entries, 1)
// balance decreased
require.Equal(t, sdk.NewInt(3), rd.Balance.Amount)
require.Equal(t, sdk.NewInt(3), rd.Entries[0].Balance.Amount)
// read updated pool
newPool := keeper.GetPool(ctx)
// bonded tokens burned
@ -420,8 +401,9 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.Len(t, rd.Entries, 1)
// balance decreased, now zero
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
require.Equal(t, sdk.NewInt(0), rd.Entries[0].Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// seven bonded tokens burned
@ -441,8 +423,9 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.Len(t, rd.Entries, 1)
// balance still zero
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
require.Equal(t, sdk.NewInt(0), rd.Entries[0].Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// four more bonded tokens burned
@ -465,8 +448,9 @@ func TestSlashWithRedelegation(t *testing.T) {
// read updating redelegation
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.Len(t, rd.Entries, 1)
// balance still zero
require.Equal(t, sdk.NewInt(0), rd.Balance.Amount)
require.Equal(t, sdk.NewInt(0), rd.Entries[0].Balance.Amount)
// read updated pool
newPool = keeper.GetPool(ctx)
// no more bonded tokens burned
@ -482,19 +466,11 @@ func TestSlashBoth(t *testing.T) {
ctx, keeper, params := setupHelper(t, 10)
fraction := sdk.NewDecWithPrec(5, 1)
// set a redelegation
rdA := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 11,
// expiration timestamp (beyond which the redelegation shouldn't be slashed)
MinTime: time.Unix(0, 0),
SharesSrc: sdk.NewDec(6),
SharesDst: sdk.NewDec(6),
InitialBalance: sdk.NewInt64Coin(params.BondDenom, 6),
Balance: sdk.NewInt64Coin(params.BondDenom, 6),
}
// set a redelegation with expiration timestamp beyond which the
// redelegation shouldn't be slashed
rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11,
time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 6), sdk.NewDec(6),
sdk.NewDec(6))
keeper.SetRedelegation(ctx, rdA)
// set the associated delegation
@ -505,16 +481,10 @@ func TestSlashBoth(t *testing.T) {
}
keeper.SetDelegation(ctx, delA)
// set an unbonding delegation
ubdA := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 11,
// expiration timestamp (beyond which the unbonding delegation shouldn't be slashed)
MinTime: time.Unix(0, 0),
InitialBalance: sdk.NewInt64Coin(params.BondDenom, 4),
Balance: sdk.NewInt64Coin(params.BondDenom, 4),
}
// set an unbonding delegation with expiration timestamp (beyond which the
// unbonding delegation shouldn't be slashed)
ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11,
time.Unix(0, 0), sdk.NewInt64Coin(params.BondDenom, 4))
keeper.SetUnbondingDelegation(ctx, ubdA)
// slash validator
@ -528,8 +498,9 @@ func TestSlashBoth(t *testing.T) {
// read updating redelegation
rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.Len(t, rdA.Entries, 1)
// balance decreased
require.Equal(t, sdk.NewInt(3), rdA.Balance.Amount)
require.Equal(t, sdk.NewInt(3), rdA.Entries[0].Balance.Amount)
// read updated pool
newPool := keeper.GetPool(ctx)
// loose tokens burned

View File

@ -94,7 +94,13 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context
require.Nil(t, err)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger())
ctx = ctx.WithConsensusParams(&abci.ConsensusParams{Validator: &abci.ValidatorParams{PubKeyTypes: []string{tmtypes.ABCIPubKeyTypeEd25519}}})
ctx = ctx.WithConsensusParams(
&abci.ConsensusParams{
Validator: &abci.ValidatorParams{
PubKeyTypes: []string{tmtypes.ABCIPubKeyTypeEd25519},
},
},
)
cdc := MakeTestCodec()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)

View File

@ -296,7 +296,8 @@ func TestQueryDelegation(t *testing.T) {
require.Equal(t, delegationsRes[0], delegation)
// Query unbonging delegation
keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10))
_, err = keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10))
require.Nil(t, err)
queryBondParams = NewQueryBondsParams(addrAcc2, addrVal1)
bz, errRes = cdc.MarshalJSON(queryBondParams)
@ -347,8 +348,10 @@ func TestQueryDelegation(t *testing.T) {
require.NotNil(t, err)
// Query redelegation
redel, err := keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr, sdk.NewDec(10))
_, err = keeper.BeginRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr, sdk.NewDec(10))
require.Nil(t, err)
redel, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr)
require.True(t, found)
bz, errRes = cdc.MarshalJSON(NewQueryRedelegationParams(addrAcc2, val1.OperatorAddr, val2.OperatorAddr))
require.Nil(t, errRes)
@ -379,7 +382,7 @@ func TestQueryRedelegations(t *testing.T) {
keeper.SetValidator(ctx, val2)
keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(100)), val1, true)
keeper.ApplyAndReturnValidatorSetUpdates(ctx)
_ = keeper.ApplyAndReturnValidatorSetUpdates(ctx)
keeper.BeginRedelegation(ctx, addrAcc2, val1.GetOperator(), val2.GetOperator(), sdk.NewDec(20))
keeper.ApplyAndReturnValidatorSetUpdates(ctx)

View File

@ -59,7 +59,9 @@ func SupplyInvariants(ck bank.Keeper, k staking.Keeper,
return false
})
k.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) bool {
loose = loose.Add(sdk.NewDecFromInt(ubd.Balance.Amount))
for _, entry := range ubd.Entries {
loose = loose.Add(sdk.NewDecFromInt(entry.Balance.Amount))
}
return false
})
k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool {

View File

@ -26,6 +26,8 @@ type DVVTriplet struct {
ValidatorDstAddr sdk.ValAddress
}
//_______________________________________________________________________
// Delegation represents the bond with tokens held by an account. It is
// owned by one delegator, and is associated with the voting power of one
// pubKey.
@ -35,6 +37,17 @@ type Delegation struct {
Shares sdk.Dec `json:"shares"`
}
// NewDelegation creates a new delegation object
func NewDelegation(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress,
shares sdk.Dec) Delegation {
return Delegation{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Shares: shares,
}
}
// return the delegation
func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte {
return cdc.MustMarshalBinaryLengthPrefixed(delegation)
@ -82,14 +95,65 @@ func (d Delegation) HumanReadableString() (string, error) {
return resp, nil
}
//________________________________________________________________________
// UnbondingDelegation reflects a delegation's passive unbonding queue.
// it may hold multiple entries between the same delegator/validator
type UnbondingDelegation struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // validator unbonding from operator addr
CreationHeight int64 `json:"creation_height"` // height which the unbonding took place
MinTime time.Time `json:"min_time"` // unix time for unbonding completion
InitialBalance sdk.Coin `json:"initial_balance"` // atoms initially scheduled to receive at completion
Balance sdk.Coin `json:"balance"` // atoms to receive at completion
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator
ValidatorAddr sdk.ValAddress `json:"validator_addr"` // validator unbonding from operator addr
Entries []UnbondingDelegationEntry `json:"entries"` // unbonding delegation entries
}
// UnbondingDelegationEntry - entry to an UnbondingDelegation
type UnbondingDelegationEntry struct {
CreationHeight int64 `json:"creation_height"` // height which the unbonding took place
CompletionTime time.Time `json:"completion_time"` // unix time for unbonding completion
InitialBalance sdk.Coin `json:"initial_balance"` // atoms initially scheduled to receive at completion
Balance sdk.Coin `json:"balance"` // atoms to receive at completion
}
// IsMature - is the current entry mature
func (e UnbondingDelegationEntry) IsMature(currentTime time.Time) bool {
return !e.CompletionTime.After(currentTime)
}
// NewUnbondingDelegation - create a new unbonding delegation object
func NewUnbondingDelegation(delegatorAddr sdk.AccAddress,
validatorAddr sdk.ValAddress, creationHeight int64, minTime time.Time,
balance sdk.Coin) UnbondingDelegation {
entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance)
return UnbondingDelegation{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Entries: []UnbondingDelegationEntry{entry},
}
}
// NewUnbondingDelegation - create a new unbonding delegation object
func NewUnbondingDelegationEntry(creationHeight int64, completionTime time.Time,
balance sdk.Coin) UnbondingDelegationEntry {
return UnbondingDelegationEntry{
CreationHeight: creationHeight,
CompletionTime: completionTime,
InitialBalance: balance,
Balance: balance,
}
}
// AddEntry - append entry to the unbonding delegation
func (d *UnbondingDelegation) AddEntry(creationHeight int64,
minTime time.Time, balance sdk.Coin) {
entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance)
d.Entries = append(d.Entries, entry)
}
// RemoveEntry - remove entry at index i to the unbonding delegation
func (d *UnbondingDelegation) RemoveEntry(i int64) {
d.Entries = append(d.Entries[:i], d.Entries[i+1:]...)
}
// return the unbonding delegation
@ -126,25 +190,83 @@ func (d UnbondingDelegation) HumanReadableString() (string, error) {
resp := "Unbonding Delegation \n"
resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr)
resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr)
resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight)
resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime)
resp += fmt.Sprintf("Expected balance: %s", d.Balance.String())
for _, entry := range d.Entries {
resp += "Unbonding Delegation Entry\n"
resp += fmt.Sprintf("Creation height: %v\n", entry.CreationHeight)
resp += fmt.Sprintf("Min time to unbond (unix): %v\n", entry.CompletionTime)
resp += fmt.Sprintf("Expected balance: %s", entry.Balance.String())
}
return resp, nil
}
// Redelegation reflects a delegation's passive re-delegation queue.
type Redelegation struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // validator redelegation source operator addr
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // validator redelegation destination operator addr
CreationHeight int64 `json:"creation_height"` // height which the redelegation took place
MinTime time.Time `json:"min_time"` // unix time for redelegation completion
InitialBalance sdk.Coin `json:"initial_balance"` // initial balance when redelegation started
Balance sdk.Coin `json:"balance"` // current balance
SharesSrc sdk.Dec `json:"shares_src"` // amount of source shares redelegating
SharesDst sdk.Dec `json:"shares_dst"` // amount of destination shares redelegating
DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // delegator
ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // validator redelegation source operator addr
ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // validator redelegation destination operator addr
Entries []RedelegationEntry `json:"entries"` // redelegation entries
}
// RedelegationEntry - entry to a Redelegation
type RedelegationEntry struct {
CreationHeight int64 `json:"creation_height"` // height which the redelegation took place
CompletionTime time.Time `json:"completion_time"` // unix time for redelegation completion
InitialBalance sdk.Coin `json:"initial_balance"` // initial balance when redelegation started
Balance sdk.Coin `json:"balance"` // current balance (current value held in destination validator)
SharesSrc sdk.Dec `json:"shares_src"` // amount of source-validator shares removed by redelegation
SharesDst sdk.Dec `json:"shares_dst"` // amount of destination-validator shares created by redelegation
}
// NewRedelegation - create a new redelegation object
func NewRedelegation(delegatorAddr sdk.AccAddress, validatorSrcAddr,
validatorDstAddr sdk.ValAddress, creationHeight int64,
minTime time.Time, balance sdk.Coin,
sharesSrc, sharesDst sdk.Dec) Redelegation {
entry := NewRedelegationEntry(creationHeight,
minTime, balance, sharesSrc, sharesDst)
return Redelegation{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
Entries: []RedelegationEntry{entry},
}
}
// NewRedelegation - create a new redelegation object
func NewRedelegationEntry(creationHeight int64,
completionTime time.Time, balance sdk.Coin,
sharesSrc, sharesDst sdk.Dec) RedelegationEntry {
return RedelegationEntry{
CreationHeight: creationHeight,
CompletionTime: completionTime,
InitialBalance: balance,
Balance: balance,
SharesSrc: sharesSrc,
SharesDst: sharesDst,
}
}
// IsMature - is the current entry mature
func (e RedelegationEntry) IsMature(currentTime time.Time) bool {
return !e.CompletionTime.After(currentTime)
}
// AddEntry - append entry to the unbonding delegation
func (d *Redelegation) AddEntry(creationHeight int64,
minTime time.Time, balance sdk.Coin,
sharesSrc, sharesDst sdk.Dec) {
entry := NewRedelegationEntry(creationHeight, minTime, balance, sharesSrc, sharesDst)
d.Entries = append(d.Entries, entry)
}
// RemoveEntry - remove entry at index i to the unbonding delegation
func (d *Redelegation) RemoveEntry(i int64) {
d.Entries = append(d.Entries[:i], d.Entries[i+1:]...)
}
// return the redelegation
@ -182,11 +304,11 @@ func (d Redelegation) HumanReadableString() (string, error) {
resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr)
resp += fmt.Sprintf("Source Validator: %s\n", d.ValidatorSrcAddr)
resp += fmt.Sprintf("Destination Validator: %s\n", d.ValidatorDstAddr)
resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight)
resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime)
resp += fmt.Sprintf("Source shares: %s\n", d.SharesSrc.String())
resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String())
for _, entry := range d.Entries {
resp += fmt.Sprintf("Creation height: %v\n", entry.CreationHeight)
resp += fmt.Sprintf("Min time to unbond (unix): %v\n", entry.CompletionTime)
resp += fmt.Sprintf("Source shares: %s\n", entry.SharesSrc.String())
resp += fmt.Sprintf("Destination shares: %s", entry.SharesDst.String())
}
return resp, nil
}

View File

@ -10,16 +10,8 @@ import (
)
func TestDelegationEqual(t *testing.T) {
d1 := Delegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorAddr: addr2,
Shares: sdk.NewDec(100),
}
d2 := Delegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorAddr: addr2,
Shares: sdk.NewDec(100),
}
d1 := NewDelegation(sdk.AccAddress(addr1), addr2, sdk.NewDec(100))
d2 := d1
ok := d1.Equal(d2)
require.True(t, ok)
@ -32,11 +24,7 @@ func TestDelegationEqual(t *testing.T) {
}
func TestDelegationHumanReadableString(t *testing.T) {
d := Delegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorAddr: addr2,
Shares: sdk.NewDec(100),
}
d := NewDelegation(sdk.AccAddress(addr1), addr2, sdk.NewDec(100))
// NOTE: Being that the validator's keypair is random, we cannot test the
// actual contents of the string.
@ -46,69 +34,54 @@ func TestDelegationHumanReadableString(t *testing.T) {
}
func TestUnbondingDelegationEqual(t *testing.T) {
ud1 := UnbondingDelegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorAddr: addr2,
}
ud2 := UnbondingDelegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorAddr: addr2,
}
ubd1 := NewUnbondingDelegation(sdk.AccAddress(addr1), addr2, 0,
time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0))
ubd2 := ubd1
ok := ud1.Equal(ud2)
ok := ubd1.Equal(ubd2)
require.True(t, ok)
ud2.ValidatorAddr = addr3
ubd2.ValidatorAddr = addr3
ud2.MinTime = time.Unix(20*20*2, 0)
ok = ud1.Equal(ud2)
ubd2.Entries[0].CompletionTime = time.Unix(20*20*2, 0)
ok = ubd1.Equal(ubd2)
require.False(t, ok)
}
func TestUnbondingDelegationHumanReadableString(t *testing.T) {
ud := UnbondingDelegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorAddr: addr2,
}
ubd := NewUnbondingDelegation(sdk.AccAddress(addr1), addr2, 0,
time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0))
// NOTE: Being that the validator's keypair is random, we cannot test the
// actual contents of the string.
valStr, err := ud.HumanReadableString()
valStr, err := ubd.HumanReadableString()
require.Nil(t, err)
require.NotEmpty(t, valStr)
}
func TestRedelegationEqual(t *testing.T) {
r1 := Redelegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorSrcAddr: addr2,
ValidatorDstAddr: addr3,
}
r2 := Redelegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorSrcAddr: addr2,
ValidatorDstAddr: addr3,
}
r1 := NewRedelegation(sdk.AccAddress(addr1), addr2, addr3, 0,
time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0),
sdk.NewDec(0), sdk.NewDec(0))
r2 := NewRedelegation(sdk.AccAddress(addr1), addr2, addr3, 0,
time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0),
sdk.NewDec(0), sdk.NewDec(0))
ok := r1.Equal(r2)
require.True(t, ok)
r2.SharesDst = sdk.NewDec(10)
r2.SharesSrc = sdk.NewDec(20)
r2.MinTime = time.Unix(20*20*2, 0)
r2.Entries[0].SharesDst = sdk.NewDec(10)
r2.Entries[0].SharesSrc = sdk.NewDec(20)
r2.Entries[0].CompletionTime = time.Unix(20*20*2, 0)
ok = r1.Equal(r2)
require.False(t, ok)
}
func TestRedelegationHumanReadableString(t *testing.T) {
r := Redelegation{
DelegatorAddr: sdk.AccAddress(addr1),
ValidatorSrcAddr: addr2,
ValidatorDstAddr: addr3,
SharesDst: sdk.NewDec(10),
SharesSrc: sdk.NewDec(20),
}
r := NewRedelegation(sdk.AccAddress(addr1), addr2, addr3, 0,
time.Unix(0, 0), sdk.NewInt64Coin(DefaultBondDenom, 0),
sdk.NewDec(10), sdk.NewDec(20))
// NOTE: Being that the validator's keypair is random, we cannot test the
// actual contents of the string.

View File

@ -178,11 +178,6 @@ func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error {
"redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation")
}
func ErrConflictingRedelegation(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation,
"conflicting redelegation from this source validator to this dest validator already exists, you must wait for it to finish")
}
func ErrDelegatorShareExRateInvalid(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation,
"cannot delegate to validators with invalid (zero) ex-rate")