Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 34 commits into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dff6681
remove kv seperation for marshalling
rigelrozanski Jan 5, 2019
1170ee3
pending
rigelrozanski Jan 5, 2019
4b90e7f
cleanup
rigelrozanski Jan 6, 2019
017dab1
cleanup x2
rigelrozanski Jan 6, 2019
270fe04
pending
rigelrozanski Jan 6, 2019
9436a19
working
rigelrozanski Jan 6, 2019
3a656a2
minor refactors
rigelrozanski Jan 6, 2019
e1e2469
entry structs defined
rigelrozanski Jan 6, 2019
7ad6dd7
uncompiled mechanism written
rigelrozanski Jan 7, 2019
cae553e
add many compile fixes
rigelrozanski Jan 7, 2019
a2cb204
Merge remote-tracking branch 'origin/develop' into rigel/delegation-i…
rigelrozanski Jan 7, 2019
e48fb95
code compiles
rigelrozanski Jan 8, 2019
e490924
fix test compile errors
rigelrozanski Jan 8, 2019
9dd1181
test cover passes
rigelrozanski Jan 8, 2019
b4d5117
...
rigelrozanski Jan 9, 2019
36761e6
Merge remote-tracking branch 'origin/develop' into rigel/delegation-i…
rigelrozanski Jan 9, 2019
31a526e
Merge remote-tracking branch 'origin/develop' into rigel/delegation-i…
rigelrozanski Jan 9, 2019
33ce0f3
multiple entries fix
rigelrozanski Jan 9, 2019
41a4285
...
rigelrozanski Jan 9, 2019
96a28d5
more design fix
rigelrozanski Jan 9, 2019
8f20ed7
working
rigelrozanski Jan 9, 2019
069c5a4
fix test cover bug
rigelrozanski Jan 10, 2019
c161cb5
Update PENDING.md
rigelrozanski Jan 10, 2019
c83b910
Merge branch 'develop' into rigel/delegation-index
rigelrozanski Jan 10, 2019
f13afea
update comment around queue completion for redelegations/ubds
rigelrozanski Jan 10, 2019
d1a2ff7
Merge remote-tracking branch 'refs/remotes/origin/rigel/delegation-in…
rigelrozanski Jan 10, 2019
8906710
basic spec updates
rigelrozanski Jan 10, 2019
59b64c9
remove ErrConflictingRedelegation
rigelrozanski Jan 11, 2019
03631dd
@cwgoes comments are resolved
rigelrozanski Jan 11, 2019
7fd4b52
Merge remote-tracking branch 'origin/develop' into rigel/delegation-i…
rigelrozanski Jan 11, 2019
9a0d49b
Update x/staking/keeper/slash.go
jackzampolin Jan 16, 2019
60a0e1c
address @alexanderbez comments
rigelrozanski Jan 16, 2019
10681df
Merge branch 'rigel/delegation-index' of https://github.com/cosmos/co…
rigelrozanski Jan 16, 2019
7c227e6
Merge remote-tracking branch 'origin/develop' into rigel/delegation-i…
rigelrozanski Jan 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
// 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)
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
}
}

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