Skip to content

Commit

Permalink
Merge PR #3243: allow multiple simultaneous redelegations/ubds betwee…
Browse files Browse the repository at this point in the history
…n 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
  • Loading branch information
rigelrozanski authored Jan 16, 2019
1 parent 916ea85 commit 133934a
Show file tree
Hide file tree
Showing 19 changed files with 754 additions and 426 deletions.
8 changes: 5 additions & 3 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
20 changes: 13 additions & 7 deletions client/lcd/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 7 additions & 2 deletions client/lcd/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions cmd/gaia/app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand Down
3 changes: 2 additions & 1 deletion cmd/gaia/cli_test/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
65 changes: 43 additions & 22 deletions docs/spec/staking/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -107,56 +107,67 @@ 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
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
}
```

### Redelegation

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
Expand All @@ -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
}
```
23 changes: 11 additions & 12 deletions x/staking/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package staking

import (
"fmt"
"sort"

abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
Expand All @@ -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)
Expand All @@ -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
Expand Down
17 changes: 9 additions & 8 deletions x/staking/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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}
Expand Down
Loading

0 comments on commit 133934a

Please sign in to comment.