Skip to content

Commit

Permalink
Merge pull request #2 from jtremback/CCV-changes-option-2A
Browse files Browse the repository at this point in the history
Staking integration for interchain security
  • Loading branch information
jtremback authored Nov 8, 2021
2 parents d83a3bf + afe3c95 commit 1803771
Show file tree
Hide file tree
Showing 15 changed files with 896 additions and 573 deletions.
2 changes: 2 additions & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -8533,6 +8533,8 @@ UnbondingDelegationEntry defines an unbonding object with relevant metadata.
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | completion_time is the unix time for unbonding completion. |
| `initial_balance` | [string](#string) | | initial_balance defines the tokens initially scheduled to receive at completion. |
| `balance` | [string](#string) | | balance defines the tokens to receive at completion. |
| `id` | [uint64](#uint64) | | Incrementing id that uniquely identifies this entry |
| `on_hold` | [bool](#bool) | | True if this entry's unbonding has been stopped by an external module |



Expand Down
6 changes: 6 additions & 0 deletions proto/cosmos/staking/v1beta1/staking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ message UnbondingDelegationEntry {
];
// balance defines the tokens to receive at completion.
string balance = 4 [(cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];

// Incrementing id that uniquely identifies this entry
uint64 id = 5;

// True if this entry's unbonding has been stopped by an external module
bool on_hold = 6;
}

// RedelegationEntry defines a redelegation object with relevant metadata.
Expand Down
7 changes: 7 additions & 0 deletions x/distribution/keeper/hooks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -119,3 +121,8 @@ func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _
func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) error {
return nil
}
func (h Hooks) UnbondingDelegationEntryCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress, _ int64, _ time.Time, _ sdk.Int, _ uint64) {
}
func (h Hooks) BeforeUnbondingDelegationEntryComplete(_ sdk.Context, _ uint64) bool {
return false
}
5 changes: 5 additions & 0 deletions x/slashing/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,8 @@ func (h Hooks) AfterDelegationModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.Va
return nil
}
func (h Hooks) BeforeValidatorSlashed(_ sdk.Context, _ sdk.ValAddress, _ sdk.Dec) error { return nil }
func (h Hooks) UnbondingDelegationEntryCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress, _ int64, _ time.Time, _ sdk.Int, _ uint64) {
}
func (h Hooks) BeforeUnbondingDelegationEntryComplete(_ sdk.Context, _ uint64) bool {
return false
}
170 changes: 156 additions & 14 deletions x/staking/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"bytes"
"encoding/binary"
"fmt"
"time"

Expand All @@ -10,6 +11,28 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking/types"
)

// Increments and returns a unique ID for an UnbondingDelegationEntry
func (k Keeper) IncrementUnbondingDelegationEntryId(ctx sdk.Context) (unbondingDelegationEntryId uint64) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.UnbondingDelegationEntryIdKey)

if bz == nil {
unbondingDelegationEntryId = 0
} else {
unbondingDelegationEntryId = binary.BigEndian.Uint64(bz)
}

unbondingDelegationEntryId = unbondingDelegationEntryId + 1

// Convert back into bytes for storage
bz = make([]byte, 8)
binary.BigEndian.PutUint64(bz, unbondingDelegationEntryId)

store.Set(types.UnbondingDelegationEntryIdKey, bz)

return unbondingDelegationEntryId
}

// return a specific delegation
func (k Keeper) GetDelegation(ctx sdk.Context,
delAddr sdk.AccAddress, valAddr sdk.ValAddress) (delegation types.Delegation, found bool) {
Expand Down Expand Up @@ -154,6 +177,58 @@ func (k Keeper) GetUnbondingDelegation(
return ubd, true
}

// return a unbonding delegation that has an unbonding delegation entry with a certain ID
func (k Keeper) GetUnbondingDelegationByEntry(
ctx sdk.Context, id uint64,
) (ubd types.UnbondingDelegation, found bool) {
store := ctx.KVStore(k.storeKey)
indexKey := types.GetUnbondingDelegationEntryIndexKey(id)
ubdeKey := store.Get(indexKey)

if ubdeKey == nil {
return ubd, false
}

value := store.Get(ubdeKey)

if value == nil {
return ubd, false
}

ubd = types.MustUnmarshalUBD(k.cdc, value)

return ubd, true
}

// Set an index to look up an UnbondingDelegation by the ID of an UnbondingDelegationEntry that it contains
func (k Keeper) SetUBDByEntryIndex(ctx sdk.Context, ubd types.UnbondingDelegation, id uint64) {
store := ctx.KVStore(k.storeKey)

delAddr, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress)
if err != nil {
panic(err)
}

valAddr, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress)
if err != nil {
panic(err)
}

indexKey := types.GetUnbondingDelegationEntryIndexKey(id)
ubdKey := types.GetUBDKey(delAddr, valAddr)

store.Set(indexKey, ubdKey)
}

// Remove a UBDByEntryIndex
func (k Keeper) DeleteUBDByEntryIndex(ctx sdk.Context, id uint64) {
store := ctx.KVStore(k.storeKey)

indexKey := types.GetUnbondingDelegationEntryIndexKey(id)

store.Delete(indexKey)
}

// return all unbonding delegations from a particular validator
func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAddress) (ubds []types.UnbondingDelegation) {
store := ctx.KVStore(k.storeKey)
Expand Down Expand Up @@ -239,14 +314,21 @@ func (k Keeper) SetUnbondingDelegationEntry(
creationHeight int64, minTime time.Time, balance sdk.Int,
) types.UnbondingDelegation {
ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr)
id := k.IncrementUnbondingDelegationEntryId(ctx)
if found {
ubd.AddEntry(creationHeight, minTime, balance)
ubd.AddEntry(creationHeight, minTime, balance, id)
} else {
ubd = types.NewUnbondingDelegation(delegatorAddr, validatorAddr, creationHeight, minTime, balance)
ubd = types.NewUnbondingDelegation(delegatorAddr, validatorAddr, creationHeight, minTime, balance, id)
}

k.SetUnbondingDelegation(ctx, ubd)

// Add to the UBDByEntry index to look up the UBD by the UBDE ID
k.SetUBDByEntryIndex(ctx, ubd, id)

// Call hook
k.UnbondingDelegationEntryCreated(ctx, delegatorAddr, validatorAddr, creationHeight, minTime, balance, id)

return ubd
}

Expand Down Expand Up @@ -787,35 +869,95 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd
return nil, err
}

// loop through all the entries and complete unbonding mature entries
// loop through all the entries and try to complete unbonding mature entries
for i := 0; i < len(ubd.Entries); i++ {
entry := ubd.Entries[i]
if entry.IsMature(ctxTime) {
// This hook allows external modules to stop an unbonding delegation entry from fully unbonding
// when the completionTime has passed. This allows them to prolong the unbonding period.
onHold := k.BeforeUnbondingDelegationEntryComplete(ctx, entry.Id)
if onHold {
// Mark that the entry is stopped
entry.OnHold = true
} else {
// Proceed with unbonding
ubd.RemoveEntry(int64(i))
i--

// track undelegation only when remaining or truncated shares are non-zero
if !entry.Balance.IsZero() {
amt := sdk.NewCoin(bondDenom, entry.Balance)
if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(
ctx, types.NotBondedPoolName, delegatorAddress, sdk.NewCoins(amt),
); err != nil {
return nil, err
}

balances = balances.Add(amt)
}
}
}
}

// 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 balances, nil
}

// This can be called to complete the unbonding of an unbonding delegation entry that was previously
// stopped by the BeforeUnbondingDelegationEntryComplete hook in CompleteUnbonding
func (k Keeper) CompleteStoppedUnbonding(ctx sdk.Context, id uint64) (found bool, err error) {
ubd, found := k.GetUnbondingDelegationByEntry(ctx, id)
if !found {
return false, nil
}

for i, entry := range ubd.Entries {
// we find the entry with the right ID and make sure that it
// is on hold.
if entry.Id == id && entry.OnHold {
// Complete unbonding
delegatorAddress, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress)
if err != nil {
return true, err
}

bondDenom := k.GetParams(ctx).BondDenom

// Remove entry
ubd.RemoveEntry(int64(i))
i--
// Remove from the UBDByEntry index
k.DeleteUBDByEntryIndex(ctx, entry.Id)

// track undelegation only when remaining or truncated shares are non-zero
if !entry.Balance.IsZero() {
amt := sdk.NewCoin(bondDenom, entry.Balance)
if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(
ctx, types.NotBondedPoolName, delegatorAddress, sdk.NewCoins(amt),
); err != nil {
return nil, err
return false, err
}
}

balances = balances.Add(amt)
// 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)
}
}
}

// 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)
// Successfully completed unbonding
return true, nil
}
}

return balances, nil
// If an entry was not found
return false, nil
}

// begin unbonding / redelegation; create a redelegation record
Expand Down
1 change: 1 addition & 0 deletions x/staking/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func TestUnbondingDelegation(t *testing.T) {
0,
time.Unix(0, 0).UTC(),
sdk.NewInt(5),
app.StakingKeeper.IncrementUnbondingDelegationEntryId(ctx),
)

// set and retrieve a record
Expand Down
19 changes: 19 additions & 0 deletions x/staking/keeper/hooks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
Expand Down Expand Up @@ -87,3 +89,20 @@ func (k Keeper) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress,
}
return nil
}

// This is called when an UnbondingDelegationEntry is first created
func (k Keeper) UnbondingDelegationEntryCreated(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress,
creationHeight int64, completionTime time.Time, balance sdk.Int, id uint64) {
if k.hooks != nil {
k.hooks.UnbondingDelegationEntryCreated(ctx, delegatorAddr, validatorAddr, creationHeight, completionTime, balance, id)
}
}

// This is called before completing unbonding of a UnbondingDelegationEntry. returning true
// will stop the unbonding.
func (k Keeper) BeforeUnbondingDelegationEntryComplete(ctx sdk.Context, id uint64) bool {
if k.hooks != nil {
return k.hooks.BeforeUnbondingDelegationEntryComplete(ctx, id)
}
return false
}
6 changes: 3 additions & 3 deletions x/staking/keeper/slash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestSlashUnbondingDelegation(t *testing.T) {
// 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.NewInt(10))
time.Unix(5, 0), sdk.NewInt(10), app.StakingKeeper.IncrementUnbondingDelegationEntryId(ctx))

app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)

Expand Down Expand Up @@ -265,7 +265,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
// set an unbonding delegation with expiration timestamp beyond which the
// unbonding delegation shouldn't be slashed
ubdTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 4)
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, time.Unix(0, 0), ubdTokens)
ubd := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11, time.Unix(0, 0), ubdTokens, app.StakingKeeper.IncrementUnbondingDelegationEntryId(ctx))
app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)

// slash validator for the first time
Expand Down Expand Up @@ -556,7 +556,7 @@ func TestSlashBoth(t *testing.T) {
// unbonding delegation shouldn't be slashed)
ubdATokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 4)
ubdA := types.NewUnbondingDelegation(addrDels[0], addrVals[0], 11,
time.Unix(0, 0), ubdATokens)
time.Unix(0, 0), ubdATokens, app.StakingKeeper.IncrementUnbondingDelegationEntryId(ctx))
app.StakingKeeper.SetUnbondingDelegation(ctx, ubdA)

bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdATokens.MulRaw(2)))
Expand Down
2 changes: 1 addition & 1 deletion x/staking/simulation/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestDecodeStore(t *testing.T) {
val, err := types.NewValidator(valAddr1, delPk1, types.NewDescription("test", "test", "test", "test", "test"))
require.NoError(t, err)
del := types.NewDelegation(delAddr1, valAddr1, sdk.OneDec())
ubd := types.NewUnbondingDelegation(delAddr1, valAddr1, 15, bondTime, sdk.OneInt())
ubd := types.NewUnbondingDelegation(delAddr1, valAddr1, 15, bondTime, sdk.OneInt(), 1)
red := types.NewRedelegation(delAddr1, valAddr1, valAddr1, 12, bondTime, sdk.OneInt(), sdk.OneDec())

kvPairs := kv.Pairs{
Expand Down
Loading

0 comments on commit 1803771

Please sign in to comment.