feat!: IS rebase (#13122)
* feat: IS rebase (squash da53124..b45dd5f) * clean up docs * fix: fix IS tests (#13420) * revert key update * add hook error handling * impl feedback * add changelog * implements style nits Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * more nits * fix tests Co-authored-by: Simon <sainoe@users.noreply.github.com> * add godoc * add godoc Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
parent
6efbedb1c7
commit
84675a6bf1
@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### Features
|
||||
|
||||
* (x/staking) [#13122](https://github.com/cosmos/cosmos-sdk/pull/13122) Add `UnbondingCanComplete` and `PutUnbondingOnHold` to `x/staking` module.
|
||||
* [#13437](https://github.com/cosmos/cosmos-sdk/pull/13437) Add new flag `--modules-to-export` in `simd export` command to export only selected modules.
|
||||
* [#13435](https://github.com/cosmos/cosmos-sdk/pull/13435) Extend error context when a simulation fails.
|
||||
* [#13298](https://github.com/cosmos/cosmos-sdk/pull/13298) Add `AddGenesisAccount` helper func in x/auth module which helps adding accounts to genesis state.
|
||||
@ -101,6 +102,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### API Breaking Changes
|
||||
|
||||
* (x/slashing, x/staking) [#13122](https://github.com/cosmos/cosmos-sdk/pull/13122) Add the infraction a validator commited type as an argument to the `Slash` keeper method.
|
||||
* [#13437](https://github.com/cosmos/cosmos-sdk/pull/13437) Add a list of modules to export argument in `ExportAppStateAndValidators`.
|
||||
* (x/slashing) [#13427](https://github.com/cosmos/cosmos-sdk/pull/13427) Move `x/slashing/testslashing` to `x/slashing/testutil` for consistency with other modules.
|
||||
* (x/staking) [#13427](https://github.com/cosmos/cosmos-sdk/pull/13427) Move `x/staking/teststaking` to `x/staking/testutil` for consistency with other modules.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ import "google/protobuf/timestamp.proto";
|
||||
import "cosmos_proto/cosmos.proto";
|
||||
import "cosmos/base/v1beta1/coin.proto";
|
||||
import "tendermint/types/types.proto";
|
||||
import "tendermint/abci/types.proto";
|
||||
|
||||
option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types";
|
||||
|
||||
@ -124,6 +125,12 @@ message Validator {
|
||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
|
||||
// strictly positive if this validator's unbonding has been stopped by external modules
|
||||
int64 unbonding_on_hold_ref_count = 12;
|
||||
|
||||
// list of unbonding ids, each uniquely identifing an unbonding of this validator
|
||||
repeated uint64 unbonding_ids = 13;
|
||||
}
|
||||
|
||||
// BondStatus is the status of a validator.
|
||||
@ -216,7 +223,7 @@ message UnbondingDelegation {
|
||||
// validator_address is the bech32-encoded address of the validator.
|
||||
string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
// entries are the unbonding delegation entries.
|
||||
repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false]; // unbonding delegation entries
|
||||
repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false]; // unbonding delegation entries
|
||||
}
|
||||
|
||||
// UnbondingDelegationEntry defines an unbonding object with relevant metadata.
|
||||
@ -240,6 +247,11 @@ message UnbondingDelegationEntry {
|
||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
// Incrementing id that uniquely identifies this entry
|
||||
uint64 unbonding_id = 5;
|
||||
|
||||
// Strictly positive if this entry's unbonding has been stopped by external modules
|
||||
int64 unbonding_on_hold_ref_count = 6;
|
||||
}
|
||||
|
||||
// RedelegationEntry defines a redelegation object with relevant metadata.
|
||||
@ -263,6 +275,11 @@ message RedelegationEntry {
|
||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
// Incrementing id that uniquely identifies this entry
|
||||
uint64 unbonding_id = 5;
|
||||
|
||||
// Strictly positive if this entry's unbonding has been stopped by external modules
|
||||
int64 unbonding_on_hold_ref_count = 6;
|
||||
}
|
||||
|
||||
// Redelegation contains the list of a particular delegator's redelegating bonds
|
||||
@ -279,7 +296,7 @@ message Redelegation {
|
||||
// validator_dst_address is the validator redelegation destination operator address.
|
||||
string validator_dst_address = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
// entries are the redelegation entries.
|
||||
repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries
|
||||
repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries
|
||||
}
|
||||
|
||||
// Params defines the parameters for the x/staking module.
|
||||
@ -358,3 +375,19 @@ message Pool {
|
||||
(gogoproto.jsontag) = "bonded_tokens"
|
||||
];
|
||||
}
|
||||
|
||||
// Infraction indicates the infraction a validator commited.
|
||||
enum Infraction {
|
||||
// UNSPECIFIED defines an empty infraction.
|
||||
INFRACTION_UNSPECIFIED = 0;
|
||||
// DOUBLE_SIGN defines a validator that double-signs a block.
|
||||
INFRACTION_DOUBLE_SIGN = 1;
|
||||
// DOWNTIME defines a validator that missed signing too many blocks.
|
||||
INFRACTION_DOWNTIME = 2;
|
||||
}
|
||||
|
||||
// ValidatorUpdates defines an array of abci.ValidatorUpdate objects.
|
||||
// TODO: explore moving this to proto/cosmos/base to separate modules from tendermint dependence
|
||||
message ValidatorUpdates {
|
||||
repeated tendermint.abci.ValidatorUpdate updates = 1 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ require (
|
||||
cosmossdk.io/simapp v0.0.0-20220908203654-84d4bf5accad
|
||||
github.com/cosmos/cosmos-sdk v0.0.0-00010101000000-000000000000
|
||||
github.com/cosmos/gogoproto v1.4.2
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
@ -68,7 +69,6 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
|
||||
@ -137,7 +137,7 @@ func TestCalculateRewardsAfterSlash(t *testing.T) {
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
|
||||
|
||||
// slash the validator by 50%
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// retrieve validator
|
||||
val = stakingKeeper.Validator(ctx, valAddrs[0])
|
||||
@ -212,7 +212,7 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) {
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
|
||||
|
||||
// slash the validator by 50%
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// fetch the validator again
|
||||
val = stakingKeeper.Validator(ctx, valAddrs[0])
|
||||
@ -226,7 +226,7 @@ func TestCalculateRewardsAfterManySlashes(t *testing.T) {
|
||||
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
|
||||
|
||||
// slash the validator by 50% again
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// fetch the validator again
|
||||
val = stakingKeeper.Validator(ctx, valAddrs[0])
|
||||
@ -466,10 +466,10 @@ func TestCalculateRewardsAfterManySlashesInSameBlock(t *testing.T) {
|
||||
distrKeeper.AllocateTokensToValidator(ctx, val, tokens)
|
||||
|
||||
// slash the validator by 50%
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// slash the validator by 50% again
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower/2, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// fetch the validator again
|
||||
val = stakingKeeper.Validator(ctx, valAddrs[0])
|
||||
@ -535,7 +535,7 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
|
||||
|
||||
// slash the validator
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
|
||||
|
||||
// second delegation
|
||||
@ -554,7 +554,7 @@ func TestCalculateRewardsMultiDelegatorMultiSlash(t *testing.T) {
|
||||
|
||||
// slash the validator again
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1))
|
||||
stakingKeeper.Slash(ctx, valConsAddr0, ctx.BlockHeight(), valPower, sdk.NewDecWithPrec(5, 1), stakingtypes.Infraction_INFRACTION_UNSPECIFIED)
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)
|
||||
|
||||
// fetch updated validator
|
||||
|
||||
@ -290,9 +290,11 @@ func (s *KeeperTestSuite) TestValidatorDippingInAndOut() {
|
||||
// shouldn't be jailed/kicked yet
|
||||
tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false)
|
||||
|
||||
// validator misses an additional 500 more blocks, after the cooling off period of SignedBlockWindow (here 1000 blocks).
|
||||
// validator misses an additional 500 more blocks within the SignedBlockWindow (here 1000 blocks).
|
||||
latest := s.slashingKeeper.SignedBlocksWindow(ctx) + height
|
||||
for ; height < latest+s.slashingKeeper.MinSignedPerWindow(ctx); height++ {
|
||||
// misses 500 blocks + within the signing windows i.e. 700-1700
|
||||
// validators misses all 1000 block of a SignedBlockWindows
|
||||
for ; height < latest+1; height++ {
|
||||
ctx = ctx.WithBlockHeight(height)
|
||||
s.slashingKeeper.HandleValidatorSignature(ctx, val.Address(), newPower, false)
|
||||
}
|
||||
@ -305,8 +307,8 @@ func (s *KeeperTestSuite) TestValidatorDippingInAndOut() {
|
||||
signInfo, found := s.slashingKeeper.GetValidatorSigningInfo(ctx, consAddr)
|
||||
s.Require().True(found)
|
||||
s.Require().Equal(int64(700), signInfo.StartHeight)
|
||||
s.Require().Equal(int64(499), signInfo.MissedBlocksCounter)
|
||||
s.Require().Equal(int64(499), signInfo.IndexOffset)
|
||||
s.Require().Equal(int64(0), signInfo.MissedBlocksCounter)
|
||||
s.Require().Equal(int64(0), signInfo.IndexOffset)
|
||||
|
||||
// some blocks pass
|
||||
height = int64(5000)
|
||||
|
||||
@ -45,6 +45,7 @@ func TestCancelUnbondingDelegation(t *testing.T) {
|
||||
delegatorAddr, validatorAddr, 10,
|
||||
ctx.BlockTime().Add(time.Minute*10),
|
||||
unbondingAmount.Amount,
|
||||
0,
|
||||
)
|
||||
|
||||
// set and retrieve a record
|
||||
|
||||
@ -58,7 +58,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), 0)
|
||||
|
||||
app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
|
||||
|
||||
@ -109,7 +109,7 @@ func TestSlashRedelegation(t *testing.T) {
|
||||
// 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.NewInt(10), math.LegacyNewDec(10))
|
||||
time.Unix(5, 0), sdk.NewInt(10), math.LegacyNewDec(10), 0)
|
||||
|
||||
app.StakingKeeper.SetRedelegation(ctx, rd)
|
||||
|
||||
@ -172,7 +172,7 @@ func TestSlashAtNegativeHeight(t *testing.T) {
|
||||
|
||||
validator, found := app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.True(t, found)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, -2, 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, -2, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// read updated state
|
||||
validator, found = app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
@ -203,7 +203,7 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) {
|
||||
|
||||
validator, found := app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.True(t, found)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// read updated state
|
||||
validator, found = app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
@ -233,7 +233,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, 0)
|
||||
app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
|
||||
|
||||
// slash validator for the first time
|
||||
@ -243,7 +243,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
|
||||
|
||||
validator, found := app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.True(t, found)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 10, 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 10, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
// end block
|
||||
applyValidatorSetUpdates(t, ctx, app.StakingKeeper, 1)
|
||||
@ -273,7 +273,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
|
||||
|
||||
// slash validator again
|
||||
ctx = ctx.WithBlockHeight(13)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 9, 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 9, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
ubd, found = app.StakingKeeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
|
||||
require.True(t, found)
|
||||
@ -299,7 +299,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
|
||||
// on the unbonding delegation, but it will slash stake bonded since the infraction
|
||||
// this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440
|
||||
ctx = ctx.WithBlockHeight(13)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 9, 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 9, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
ubd, found = app.StakingKeeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
|
||||
require.True(t, found)
|
||||
@ -325,7 +325,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) {
|
||||
// on the unbonding delegation, but it will slash stake bonded since the infraction
|
||||
// this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440
|
||||
ctx = ctx.WithBlockHeight(13)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 9, 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 9, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
ubd, found = app.StakingKeeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
|
||||
require.True(t, found)
|
||||
@ -358,7 +358,7 @@ func TestSlashWithRedelegation(t *testing.T) {
|
||||
|
||||
// set a redelegation
|
||||
rdTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 6)
|
||||
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdTokens, sdk.NewDecFromInt(rdTokens))
|
||||
rd := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdTokens, sdk.NewDecFromInt(rdTokens), 0)
|
||||
app.StakingKeeper.SetRedelegation(ctx, rd)
|
||||
|
||||
// set the associated delegation
|
||||
@ -382,7 +382,9 @@ func TestSlashWithRedelegation(t *testing.T) {
|
||||
validator, found := app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.True(t, found)
|
||||
|
||||
require.NotPanics(t, func() { app.StakingKeeper.Slash(ctx, consAddr, 10, 10, fraction) })
|
||||
require.NotPanics(t, func() {
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 10, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
})
|
||||
burnAmount := sdk.NewDecFromInt(app.StakingKeeper.TokensFromConsensusPower(ctx, 10)).Mul(fraction).TruncateInt()
|
||||
|
||||
bondedPool = app.StakingKeeper.GetBondedPool(ctx)
|
||||
@ -413,7 +415,9 @@ func TestSlashWithRedelegation(t *testing.T) {
|
||||
validator, found = app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.True(t, found)
|
||||
|
||||
require.NotPanics(t, func() { app.StakingKeeper.Slash(ctx, consAddr, 10, 10, math.LegacyOneDec()) })
|
||||
require.NotPanics(t, func() {
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 10, 10, math.LegacyOneDec(), types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
})
|
||||
burnAmount = app.StakingKeeper.TokensFromConsensusPower(ctx, 7)
|
||||
|
||||
// read updated pool
|
||||
@ -447,7 +451,9 @@ func TestSlashWithRedelegation(t *testing.T) {
|
||||
validator, found = app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.True(t, found)
|
||||
|
||||
require.NotPanics(t, func() { app.StakingKeeper.Slash(ctx, consAddr, 10, 10, math.LegacyOneDec()) })
|
||||
require.NotPanics(t, func() {
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 10, 10, math.LegacyOneDec(), types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
})
|
||||
|
||||
burnAmount = sdk.NewDecFromInt(app.StakingKeeper.TokensFromConsensusPower(ctx, 10)).Mul(math.LegacyOneDec()).TruncateInt()
|
||||
burnAmount = burnAmount.Sub(math.LegacyOneDec().MulInt(rdTokens).TruncateInt())
|
||||
@ -480,7 +486,9 @@ func TestSlashWithRedelegation(t *testing.T) {
|
||||
validator, _ = app.StakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
||||
require.Equal(t, validator.GetStatus(), types.Unbonding)
|
||||
|
||||
require.NotPanics(t, func() { app.StakingKeeper.Slash(ctx, consAddr, 10, 10, math.LegacyOneDec()) })
|
||||
require.NotPanics(t, func() {
|
||||
app.StakingKeeper.Slash(ctx, consAddr, 10, 10, math.LegacyOneDec(), types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
})
|
||||
|
||||
// read updated pool
|
||||
bondedPool = app.StakingKeeper.GetBondedPool(ctx)
|
||||
@ -510,7 +518,7 @@ func TestSlashBoth(t *testing.T) {
|
||||
// set a redelegation with expiration timestamp beyond which the
|
||||
// redelegation shouldn't be slashed
|
||||
rdATokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 6)
|
||||
rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdATokens, sdk.NewDecFromInt(rdATokens))
|
||||
rdA := types.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 11, time.Unix(0, 0), rdATokens, sdk.NewDecFromInt(rdATokens), 0)
|
||||
app.StakingKeeper.SetRedelegation(ctx, rdA)
|
||||
|
||||
// set the associated delegation
|
||||
@ -521,7 +529,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, 0)
|
||||
app.StakingKeeper.SetUnbondingDelegation(ctx, ubdA)
|
||||
|
||||
bondedCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, rdATokens.MulRaw(2)))
|
||||
@ -544,7 +552,7 @@ func TestSlashBoth(t *testing.T) {
|
||||
validator, found := app.StakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0]))
|
||||
require.True(t, found)
|
||||
consAddr0 := sdk.ConsAddress(PKs[0].Address())
|
||||
app.StakingKeeper.Slash(ctx, consAddr0, 10, 10, fraction)
|
||||
app.StakingKeeper.Slash(ctx, consAddr0, 10, 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
|
||||
burnedNotBondedAmount := fraction.MulInt(ubdATokens).TruncateInt()
|
||||
burnedBondAmount := sdk.NewDecFromInt(app.StakingKeeper.TokensFromConsensusPower(ctx, 10)).Mul(fraction).TruncateInt()
|
||||
@ -575,11 +583,11 @@ func TestSlashAmount(t *testing.T) {
|
||||
app, ctx, _, _ := bootstrapSlashTest(t, 10)
|
||||
consAddr := sdk.ConsAddress(PKs[0].Address())
|
||||
fraction := sdk.NewDecWithPrec(5, 1)
|
||||
burnedCoins := app.StakingKeeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction)
|
||||
burnedCoins := app.StakingKeeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
require.True(t, burnedCoins.GT(math.ZeroInt()))
|
||||
|
||||
// test the case where the validator was not found, which should return no coins
|
||||
_, addrVals := generateAddresses(app, ctx, 100)
|
||||
noBurned := app.StakingKeeper.Slash(ctx, sdk.ConsAddress(addrVals[0]), ctx.BlockHeight(), 10, fraction)
|
||||
noBurned := app.StakingKeeper.Slash(ctx, sdk.ConsAddress(addrVals[0]), ctx.BlockHeight(), 10, fraction, types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
require.True(t, sdk.NewInt(0).Equal(noBurned))
|
||||
}
|
||||
|
||||
417
tests/integration/staking/keeper/unbonding_test.go
Normal file
417
tests/integration/staking/keeper/unbonding_test.go
Normal file
@ -0,0 +1,417 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"cosmossdk.io/simapp"
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil"
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/testutil"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// SetupUnbondingTests creates two validators and setup mocked staking hooks for testing unbonding
|
||||
func SetupUnbondingTests(t *testing.T, app *simapp.SimApp, ctx sdk.Context, hookCalled *bool, ubdeID *uint64) (bondDenom string, addrDels []sdk.AccAddress, addrVals []sdk.ValAddress) {
|
||||
// setup hooks
|
||||
mockCtrl := gomock.NewController(t)
|
||||
mockStackingHooks := testutil.NewMockStakingHooks(mockCtrl)
|
||||
mockStackingHooks.EXPECT().AfterDelegationModified(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().AfterUnbondingInitiated(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx sdk.Context, id uint64) error {
|
||||
*hookCalled = true
|
||||
// save id
|
||||
*ubdeID = id
|
||||
// call back to stop unbonding
|
||||
err := app.StakingKeeper.PutUnbondingOnHold(ctx, id)
|
||||
require.NoError(t, err)
|
||||
|
||||
return nil
|
||||
}).AnyTimes()
|
||||
mockStackingHooks.EXPECT().AfterValidatorBeginUnbonding(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().AfterValidatorBonded(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().AfterValidatorCreated(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().AfterValidatorRemoved(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().BeforeDelegationCreated(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().BeforeDelegationRemoved(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().BeforeDelegationSharesModified(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().BeforeValidatorModified(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
mockStackingHooks.EXPECT().BeforeValidatorSlashed(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||
app.StakingKeeper.SetHooks(types.NewMultiStakingHooks(mockStackingHooks))
|
||||
|
||||
addrDels = simtestutil.AddTestAddrsIncremental(app.BankKeeper, app.StakingKeeper, ctx, 2, math.NewInt(10000))
|
||||
addrVals = simtestutil.ConvertAddrsToValAddrs(addrDels)
|
||||
|
||||
valTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 10)
|
||||
startTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 20)
|
||||
|
||||
bondDenom = app.StakingKeeper.BondDenom(ctx)
|
||||
notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx)
|
||||
|
||||
require.NoError(t, banktestutil.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens))))
|
||||
app.BankKeeper.SendCoinsFromModuleToModule(ctx, types.BondedPoolName, types.NotBondedPoolName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, startTokens)))
|
||||
app.AccountKeeper.SetModuleAccount(ctx, notBondedPool)
|
||||
|
||||
// Create a validator
|
||||
validator1 := testutil.NewValidator(t, addrVals[0], PKs[0])
|
||||
validator1, issuedShares1 := validator1.AddTokensFromDel(valTokens)
|
||||
require.Equal(t, valTokens, issuedShares1.RoundInt())
|
||||
|
||||
validator1 = stakingkeeper.TestingUpdateValidator(app.StakingKeeper, ctx, validator1, true)
|
||||
require.True(math.IntEq(t, valTokens, validator1.BondedTokens()))
|
||||
require.True(t, validator1.IsBonded())
|
||||
|
||||
// Create a delegator
|
||||
delegation := types.NewDelegation(addrDels[0], addrVals[0], issuedShares1)
|
||||
app.StakingKeeper.SetDelegation(ctx, delegation)
|
||||
|
||||
// Create a validator to redelegate to
|
||||
validator2 := testutil.NewValidator(t, addrVals[1], PKs[1])
|
||||
validator2, issuedShares2 := validator2.AddTokensFromDel(valTokens)
|
||||
require.Equal(t, valTokens, issuedShares2.RoundInt())
|
||||
|
||||
validator2 = stakingkeeper.TestingUpdateValidator(app.StakingKeeper, ctx, validator2, true)
|
||||
require.Equal(t, types.Bonded, validator2.Status)
|
||||
require.True(t, validator2.IsBonded())
|
||||
|
||||
return bondDenom, addrDels, addrVals
|
||||
}
|
||||
|
||||
func doUnbondingDelegation(
|
||||
t *testing.T,
|
||||
stakingKeeper *stakingkeeper.Keeper,
|
||||
bankKeeper types.BankKeeper,
|
||||
ctx sdk.Context,
|
||||
bondDenom string,
|
||||
addrDels []sdk.AccAddress,
|
||||
addrVals []sdk.ValAddress,
|
||||
hookCalled *bool,
|
||||
) (completionTime time.Time, bondedAmt math.Int, notBondedAmt math.Int) {
|
||||
// UNDELEGATE
|
||||
// Save original bonded and unbonded amounts
|
||||
bondedAmt1 := bankKeeper.GetBalance(ctx, stakingKeeper.GetBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
notBondedAmt1 := bankKeeper.GetBalance(ctx, stakingKeeper.GetNotBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
|
||||
var err error
|
||||
completionTime, err = stakingKeeper.Undelegate(ctx, addrDels[0], addrVals[0], sdk.NewDec(1))
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that the unbonding actually happened
|
||||
bondedAmt2 := bankKeeper.GetBalance(ctx, stakingKeeper.GetBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
notBondedAmt2 := bankKeeper.GetBalance(ctx, stakingKeeper.GetNotBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
// Bonded amount is less
|
||||
require.True(math.IntEq(t, bondedAmt1.SubRaw(1), bondedAmt2))
|
||||
// Unbonded amount is more
|
||||
require.True(math.IntEq(t, notBondedAmt1.AddRaw(1), notBondedAmt2))
|
||||
|
||||
// Check that the unbonding happened- we look up the entry and see that it has the correct number of shares
|
||||
unbondingDelegations := stakingKeeper.GetUnbondingDelegationsFromValidator(ctx, addrVals[0])
|
||||
require.Equal(t, math.NewInt(1), unbondingDelegations[0].Entries[0].Balance)
|
||||
|
||||
// check that our hook was called
|
||||
require.True(t, *hookCalled)
|
||||
|
||||
return completionTime, bondedAmt2, notBondedAmt2
|
||||
}
|
||||
|
||||
func doRedelegation(
|
||||
t *testing.T,
|
||||
stakingKeeper *stakingkeeper.Keeper,
|
||||
ctx sdk.Context,
|
||||
addrDels []sdk.AccAddress,
|
||||
addrVals []sdk.ValAddress,
|
||||
hookCalled *bool,
|
||||
) (completionTime time.Time) {
|
||||
var err error
|
||||
completionTime, err = stakingKeeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(1))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the redelegation happened- we look up the entry and see that it has the correct number of shares
|
||||
redelegations := stakingKeeper.GetRedelegationsFromSrcValidator(ctx, addrVals[0])
|
||||
require.Equal(t, 1, len(redelegations))
|
||||
require.Equal(t, sdk.NewDec(1), redelegations[0].Entries[0].SharesDst)
|
||||
|
||||
// check that our hook was called
|
||||
require.True(t, *hookCalled)
|
||||
|
||||
return completionTime
|
||||
}
|
||||
|
||||
func doValidatorUnbonding(
|
||||
t *testing.T,
|
||||
stakingKeeper *stakingkeeper.Keeper,
|
||||
ctx sdk.Context,
|
||||
addrVal sdk.ValAddress,
|
||||
hookCalled *bool,
|
||||
) (validator types.Validator) {
|
||||
validator, found := stakingKeeper.GetValidator(ctx, addrVal)
|
||||
require.True(t, found)
|
||||
// Check that status is bonded
|
||||
require.Equal(t, types.BondStatus(3), validator.Status)
|
||||
|
||||
validator, err := stakingKeeper.BeginUnbondingValidator(ctx, validator)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that status is unbonding
|
||||
require.Equal(t, types.BondStatus(2), validator.Status)
|
||||
|
||||
// check that our hook was called
|
||||
require.True(t, *hookCalled)
|
||||
|
||||
return validator
|
||||
}
|
||||
|
||||
func TestValidatorUnbondingOnHold1(t *testing.T) {
|
||||
var (
|
||||
hookCalled bool
|
||||
ubdeID uint64
|
||||
)
|
||||
|
||||
_, app, ctx := createTestInput(t)
|
||||
_, _, addrVals := SetupUnbondingTests(t, app, ctx, &hookCalled, &ubdeID)
|
||||
|
||||
// Start unbonding first validator
|
||||
validator := doValidatorUnbonding(t, app.StakingKeeper, ctx, addrVals[0], &hookCalled)
|
||||
|
||||
completionTime := validator.UnbondingTime
|
||||
completionHeight := validator.UnbondingHeight
|
||||
|
||||
// CONSUMER CHAIN'S UNBONDING PERIOD ENDS - STOPPED UNBONDING CAN NOW COMPLETE
|
||||
err := app.StakingKeeper.UnbondingCanComplete(ctx, ubdeID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to unbond validator
|
||||
app.StakingKeeper.UnbondAllMatureValidators(ctx)
|
||||
|
||||
// Check that validator unbonding is not complete (is not mature yet)
|
||||
validator, found := app.StakingKeeper.GetValidator(ctx, addrVals[0])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonding, validator.Status)
|
||||
unbondingVals := app.StakingKeeper.GetUnbondingValidators(ctx, completionTime, completionHeight)
|
||||
require.Equal(t, 1, len(unbondingVals))
|
||||
require.Equal(t, validator.OperatorAddress, unbondingVals[0])
|
||||
|
||||
// PROVIDER CHAIN'S UNBONDING PERIOD ENDS - BUT UNBONDING CANNOT COMPLETE
|
||||
ctx = ctx.WithBlockTime(completionTime.Add(time.Duration(1)))
|
||||
ctx = ctx.WithBlockHeight(completionHeight + 1)
|
||||
app.StakingKeeper.UnbondAllMatureValidators(ctx)
|
||||
|
||||
// Check that validator unbonding is complete
|
||||
validator, found = app.StakingKeeper.GetValidator(ctx, addrVals[0])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonded, validator.Status)
|
||||
unbondingVals = app.StakingKeeper.GetUnbondingValidators(ctx, completionTime, completionHeight)
|
||||
require.Equal(t, 0, len(unbondingVals))
|
||||
}
|
||||
|
||||
func TestValidatorUnbondingOnHold2(t *testing.T) {
|
||||
var (
|
||||
hookCalled bool
|
||||
ubdeID uint64
|
||||
ubdeIDs []uint64
|
||||
)
|
||||
|
||||
_, app, ctx := createTestInput(t)
|
||||
_, _, addrVals := SetupUnbondingTests(t, app, ctx, &hookCalled, &ubdeID)
|
||||
|
||||
// Start unbonding first validator
|
||||
validator1 := doValidatorUnbonding(t, app.StakingKeeper, ctx, addrVals[0], &hookCalled)
|
||||
ubdeIDs = append(ubdeIDs, ubdeID)
|
||||
|
||||
// Reset hookCalled flag
|
||||
hookCalled = false
|
||||
|
||||
// Start unbonding second validator
|
||||
validator2 := doValidatorUnbonding(t, app.StakingKeeper, ctx, addrVals[1], &hookCalled)
|
||||
ubdeIDs = append(ubdeIDs, ubdeID)
|
||||
|
||||
// Check that there are two unbonding operations
|
||||
require.Equal(t, 2, len(ubdeIDs))
|
||||
|
||||
// Check that both validators have same unbonding time
|
||||
require.Equal(t, validator1.UnbondingTime, validator2.UnbondingTime)
|
||||
|
||||
completionTime := validator1.UnbondingTime
|
||||
completionHeight := validator1.UnbondingHeight
|
||||
|
||||
// PROVIDER CHAIN'S UNBONDING PERIOD ENDS - BUT UNBONDING CANNOT COMPLETE
|
||||
ctx = ctx.WithBlockTime(completionTime.Add(time.Duration(1)))
|
||||
ctx = ctx.WithBlockHeight(completionHeight + 1)
|
||||
app.StakingKeeper.UnbondAllMatureValidators(ctx)
|
||||
|
||||
// Check that unbonding is not complete for both validators
|
||||
validator1, found := app.StakingKeeper.GetValidator(ctx, addrVals[0])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonding, validator1.Status)
|
||||
validator2, found = app.StakingKeeper.GetValidator(ctx, addrVals[1])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonding, validator2.Status)
|
||||
unbondingVals := app.StakingKeeper.GetUnbondingValidators(ctx, completionTime, completionHeight)
|
||||
require.Equal(t, 2, len(unbondingVals))
|
||||
require.Equal(t, validator1.OperatorAddress, unbondingVals[0])
|
||||
require.Equal(t, validator2.OperatorAddress, unbondingVals[1])
|
||||
|
||||
// CONSUMER CHAIN'S UNBONDING PERIOD ENDS - STOPPED UNBONDING CAN NOW COMPLETE
|
||||
err := app.StakingKeeper.UnbondingCanComplete(ctx, ubdeIDs[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try again to unbond validators
|
||||
app.StakingKeeper.UnbondAllMatureValidators(ctx)
|
||||
|
||||
// Check that unbonding is complete for validator1, but not for validator2
|
||||
validator1, found = app.StakingKeeper.GetValidator(ctx, addrVals[0])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonded, validator1.Status)
|
||||
validator2, found = app.StakingKeeper.GetValidator(ctx, addrVals[1])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonding, validator2.Status)
|
||||
unbondingVals = app.StakingKeeper.GetUnbondingValidators(ctx, completionTime, completionHeight)
|
||||
require.Equal(t, 1, len(unbondingVals))
|
||||
require.Equal(t, validator2.OperatorAddress, unbondingVals[0])
|
||||
|
||||
// Unbonding for validator2 can complete
|
||||
err = app.StakingKeeper.UnbondingCanComplete(ctx, ubdeIDs[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try again to unbond validators
|
||||
app.StakingKeeper.UnbondAllMatureValidators(ctx)
|
||||
|
||||
// Check that unbonding is complete for validator2
|
||||
validator2, found = app.StakingKeeper.GetValidator(ctx, addrVals[1])
|
||||
require.True(t, found)
|
||||
require.Equal(t, types.Unbonded, validator2.Status)
|
||||
unbondingVals = app.StakingKeeper.GetUnbondingValidators(ctx, completionTime, completionHeight)
|
||||
require.Equal(t, 0, len(unbondingVals))
|
||||
}
|
||||
|
||||
func TestRedelegationOnHold1(t *testing.T) {
|
||||
var (
|
||||
hookCalled bool
|
||||
ubdeID uint64
|
||||
)
|
||||
|
||||
_, app, ctx := createTestInput(t)
|
||||
_, addrDels, addrVals := SetupUnbondingTests(t, app, ctx, &hookCalled, &ubdeID)
|
||||
completionTime := doRedelegation(t, app.StakingKeeper, ctx, addrDels, addrVals, &hookCalled)
|
||||
|
||||
// CONSUMER CHAIN'S UNBONDING PERIOD ENDS - BUT UNBONDING CANNOT COMPLETE
|
||||
err := app.StakingKeeper.UnbondingCanComplete(ctx, ubdeID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Redelegation is not complete - still exists
|
||||
redelegations := app.StakingKeeper.GetRedelegationsFromSrcValidator(ctx, addrVals[0])
|
||||
require.Equal(t, 1, len(redelegations))
|
||||
|
||||
// PROVIDER CHAIN'S UNBONDING PERIOD ENDS - STOPPED UNBONDING CAN NOW COMPLETE
|
||||
ctx = ctx.WithBlockTime(completionTime)
|
||||
_, err = app.StakingKeeper.CompleteRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Redelegation is complete and record is gone
|
||||
redelegations = app.StakingKeeper.GetRedelegationsFromSrcValidator(ctx, addrVals[0])
|
||||
require.Equal(t, 0, len(redelegations))
|
||||
}
|
||||
|
||||
func TestRedelegationOnHold2(t *testing.T) {
|
||||
var (
|
||||
hookCalled bool
|
||||
ubdeID uint64
|
||||
)
|
||||
|
||||
_, app, ctx := createTestInput(t)
|
||||
_, addrDels, addrVals := SetupUnbondingTests(t, app, ctx, &hookCalled, &ubdeID)
|
||||
completionTime := doRedelegation(t, app.StakingKeeper, ctx, addrDels, addrVals, &hookCalled)
|
||||
|
||||
// PROVIDER CHAIN'S UNBONDING PERIOD ENDS - BUT UNBONDING CANNOT COMPLETE
|
||||
ctx = ctx.WithBlockTime(completionTime)
|
||||
_, err := app.StakingKeeper.CompleteRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Redelegation is not complete - still exists
|
||||
redelegations := app.StakingKeeper.GetRedelegationsFromSrcValidator(ctx, addrVals[0])
|
||||
require.Equal(t, 1, len(redelegations))
|
||||
|
||||
// CONSUMER CHAIN'S UNBONDING PERIOD ENDS - STOPPED UNBONDING CAN NOW COMPLETE
|
||||
err = app.StakingKeeper.UnbondingCanComplete(ctx, ubdeID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Redelegation is complete and record is gone
|
||||
redelegations = app.StakingKeeper.GetRedelegationsFromSrcValidator(ctx, addrVals[0])
|
||||
require.Equal(t, 0, len(redelegations))
|
||||
}
|
||||
|
||||
func TestUnbondingDelegationOnHold1(t *testing.T) {
|
||||
var (
|
||||
hookCalled bool
|
||||
ubdeID uint64
|
||||
)
|
||||
|
||||
_, app, ctx := createTestInput(t)
|
||||
bondDenom, addrDels, addrVals := SetupUnbondingTests(t, app, ctx, &hookCalled, &ubdeID)
|
||||
completionTime, bondedAmt1, notBondedAmt1 := doUnbondingDelegation(t, app.StakingKeeper, app.BankKeeper, ctx, bondDenom, addrDels, addrVals, &hookCalled)
|
||||
|
||||
// CONSUMER CHAIN'S UNBONDING PERIOD ENDS - BUT UNBONDING CANNOT COMPLETE
|
||||
err := app.StakingKeeper.UnbondingCanComplete(ctx, ubdeID)
|
||||
require.NoError(t, err)
|
||||
|
||||
bondedAmt3 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
notBondedAmt3 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetNotBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
|
||||
// Bonded and unbonded amounts are the same as before because the completionTime has not yet passed and so the
|
||||
// unbondingDelegation has not completed
|
||||
require.True(math.IntEq(t, bondedAmt1, bondedAmt3))
|
||||
require.True(math.IntEq(t, notBondedAmt1, notBondedAmt3))
|
||||
|
||||
// PROVIDER CHAIN'S UNBONDING PERIOD ENDS - STOPPED UNBONDING CAN NOW COMPLETE
|
||||
ctx = ctx.WithBlockTime(completionTime)
|
||||
_, err = app.StakingKeeper.CompleteUnbonding(ctx, addrDels[0], addrVals[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the unbonding was finally completed
|
||||
bondedAmt5 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
notBondedAmt5 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetNotBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
|
||||
require.True(math.IntEq(t, bondedAmt1, bondedAmt5))
|
||||
// Not bonded amount back to what it was originaly
|
||||
require.True(math.IntEq(t, notBondedAmt1.SubRaw(1), notBondedAmt5))
|
||||
}
|
||||
|
||||
func TestUnbondingDelegationOnHold2(t *testing.T) {
|
||||
var (
|
||||
hookCalled bool
|
||||
ubdeID uint64
|
||||
)
|
||||
|
||||
_, app, ctx := createTestInput(t)
|
||||
bondDenom, addrDels, addrVals := SetupUnbondingTests(t, app, ctx, &hookCalled, &ubdeID)
|
||||
completionTime, bondedAmt1, notBondedAmt1 := doUnbondingDelegation(t, app.StakingKeeper, app.BankKeeper, ctx, bondDenom, addrDels, addrVals, &hookCalled)
|
||||
|
||||
// PROVIDER CHAIN'S UNBONDING PERIOD ENDS - BUT UNBONDING CANNOT COMPLETE
|
||||
ctx = ctx.WithBlockTime(completionTime)
|
||||
_, err := app.StakingKeeper.CompleteUnbonding(ctx, addrDels[0], addrVals[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
bondedAmt3 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
notBondedAmt3 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetNotBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
|
||||
// Bonded and unbonded amounts are the same as before because the completionTime has not yet passed and so the
|
||||
// unbondingDelegation has not completed
|
||||
require.True(math.IntEq(t, bondedAmt1, bondedAmt3))
|
||||
require.True(math.IntEq(t, notBondedAmt1, notBondedAmt3))
|
||||
|
||||
// CONSUMER CHAIN'S UNBONDING PERIOD ENDS - STOPPED UNBONDING CAN NOW COMPLETE
|
||||
err = app.StakingKeeper.UnbondingCanComplete(ctx, ubdeID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the unbonding was finally completed
|
||||
bondedAmt5 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
notBondedAmt5 := app.BankKeeper.GetBalance(ctx, app.StakingKeeper.GetNotBondedPool(ctx).GetAddress(), bondDenom).Amount
|
||||
|
||||
require.True(math.IntEq(t, bondedAmt1, bondedAmt5))
|
||||
// Not bonded amount back to what it was originaly
|
||||
require.True(math.IntEq(t, notBondedAmt1.SubRaw(1), notBondedAmt5))
|
||||
}
|
||||
@ -150,7 +150,7 @@ func TestSlashToZeroPowerRemoved(t *testing.T) {
|
||||
require.Equal(t, valTokens, validator.Tokens, "\nvalidator %v\npool %v", validator, valTokens)
|
||||
|
||||
// slash the validator by 100%
|
||||
app.StakingKeeper.Slash(ctx, sdk.ConsAddress(PKs[0].Address()), 0, 100, math.LegacyOneDec())
|
||||
app.StakingKeeper.Slash(ctx, sdk.ConsAddress(PKs[0].Address()), 0, 100, math.LegacyOneDec(), types.Infraction_INFRACTION_UNSPECIFIED)
|
||||
// apply TM updates
|
||||
applyValidatorSetUpdates(t, ctx, app.StakingKeeper, -1)
|
||||
// validator should be unbonding
|
||||
|
||||
@ -126,3 +126,7 @@ func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _
|
||||
func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Hooks) AfterUnbondingInitiated(_ sdk.Context, _ uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -222,15 +222,7 @@ The Cosmos SDK handles two types of evidence inside the ABCI `BeginBlock`:
|
||||
|
||||
The evidence module handles these two evidence types the same way. First, the Cosmos SDK converts the Tendermint concrete evidence type to an SDK `Evidence` interface using `Equivocation` as the concrete type.
|
||||
|
||||
```proto
|
||||
// Equivocation implements the Evidence interface.
|
||||
message Equivocation {
|
||||
int64 height = 1;
|
||||
google.protobuf.Timestamp time = 2;
|
||||
int64 power = 3;
|
||||
string consensus_address = 4;
|
||||
}
|
||||
```
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/proto/cosmos/evidence/v1beta1/evidence.proto#L11-L22
|
||||
|
||||
For some `Equivocation` submitted in `block` to be valid, it must satisfy:
|
||||
|
||||
@ -252,106 +244,7 @@ validator to ever re-enter the validator set.
|
||||
|
||||
The `Equivocation` evidence is handled as follows:
|
||||
|
||||
```go
|
||||
func (k Keeper) HandleEquivocationEvidence(ctx sdk.Context, evidence *types.Equivocation) {
|
||||
logger := k.Logger(ctx)
|
||||
consAddr := evidence.GetConsensusAddress()
|
||||
|
||||
if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
|
||||
// Ignore evidence that cannot be handled.
|
||||
//
|
||||
// NOTE: We used to panic with:
|
||||
// `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
|
||||
// but this couples the expectations of the app to both Tendermint and
|
||||
// the simulator. Both are expected to provide the full range of
|
||||
// allowable but none of the disallowed evidence types. Instead of
|
||||
// getting this coordination right, it is easier to relax the
|
||||
// constraints and ignore evidence that cannot be handled.
|
||||
return
|
||||
}
|
||||
|
||||
// calculate the age of the evidence
|
||||
infractionHeight := evidence.GetHeight()
|
||||
infractionTime := evidence.GetTime()
|
||||
ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
|
||||
ageBlocks := ctx.BlockHeader().Height - infractionHeight
|
||||
|
||||
// Reject evidence if the double-sign is too old. Evidence is considered stale
|
||||
// if the difference in time and number of blocks is greater than the allowed
|
||||
// parameters defined.
|
||||
cp := ctx.ConsensusParams()
|
||||
if cp != nil && cp.Evidence != nil {
|
||||
if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
|
||||
logger.Info(
|
||||
"ignored equivocation; evidence too old",
|
||||
"validator", consAddr,
|
||||
"infraction_height", infractionHeight,
|
||||
"max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
|
||||
"infraction_time", infractionTime,
|
||||
"max_age_duration", cp.Evidence.MaxAgeDuration,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr)
|
||||
if validator == nil || validator.IsUnbonded() {
|
||||
// Defensive: Simulation doesn't take unbonding periods into account, and
|
||||
// Tendermint might break this assumption at some point.
|
||||
return
|
||||
}
|
||||
|
||||
if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok {
|
||||
panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
|
||||
}
|
||||
|
||||
// ignore if the validator is already tombstoned
|
||||
if k.slashingKeeper.IsTombstoned(ctx, consAddr) {
|
||||
logger.Info(
|
||||
"ignored equivocation; validator already tombstoned",
|
||||
"validator", consAddr,
|
||||
"infraction_height", infractionHeight,
|
||||
"infraction_time", infractionTime,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info(
|
||||
"confirmed equivocation",
|
||||
"validator", consAddr,
|
||||
"infraction_height", infractionHeight,
|
||||
"infraction_time", infractionTime,
|
||||
)
|
||||
|
||||
// We need to retrieve the stake distribution which signed the block, so we
|
||||
// subtract ValidatorUpdateDelay from the evidence height.
|
||||
// Note, that this *can* result in a negative "distributionHeight", up to
|
||||
// -ValidatorUpdateDelay, i.e. at the end of the
|
||||
// pre-genesis block (none) = at the beginning of the genesis block.
|
||||
// That's fine since this is just used to filter unbonding delegations & redelegations.
|
||||
distributionHeight := infractionHeight - sdk.ValidatorUpdateDelay
|
||||
|
||||
// Slash validator. The `power` is the int64 power of the validator as provided
|
||||
// to/by Tendermint. This value is validator.Tokens as sent to Tendermint via
|
||||
// ABCI, and now received as evidence. The fraction is passed in to separately
|
||||
// to slash unbonding and rebonding delegations.
|
||||
k.slashingKeeper.Slash(
|
||||
ctx,
|
||||
consAddr,
|
||||
k.slashingKeeper.SlashFractionDoubleSign(ctx),
|
||||
evidence.GetValidatorPower(), distributionHeight,
|
||||
)
|
||||
|
||||
// Jail the validator if not already jailed. This will begin unbonding the
|
||||
// validator if not already unbonding (tombstoned).
|
||||
if !validator.IsJailed() {
|
||||
k.slashingKeeper.Jail(ctx, consAddr)
|
||||
}
|
||||
|
||||
k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime)
|
||||
k.slashingKeeper.Tombstone(ctx, consAddr)
|
||||
}
|
||||
```
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/83260b0c2f9afcc7ec94a102f83906e8e56ef18e/x/evidence/keeper/infraction.go#L26-L140
|
||||
|
||||
**Note:** The slashing, jailing, and tombstoning calls are delegated through the `x/slashing` module
|
||||
that emits informative events and finally delegates calls to the `x/staking` module. See documentation
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/evidence/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
// HandleEquivocationEvidence implements an equivocation evidence handler. Assuming the
|
||||
@ -70,6 +71,21 @@ func (k Keeper) HandleEquivocationEvidence(ctx sdk.Context, evidence *types.Equi
|
||||
return
|
||||
}
|
||||
|
||||
if !validator.GetOperator().Empty() {
|
||||
if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
|
||||
// Ignore evidence that cannot be handled.
|
||||
//
|
||||
// NOTE: We used to panic with:
|
||||
// `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
|
||||
// but this couples the expectations of the app to both Tendermint and
|
||||
// the simulator. Both are expected to provide the full range of
|
||||
// allowable but none of the disallowed evidence types. Instead of
|
||||
// getting this coordination right, it is easier to relax the
|
||||
// constraints and ignore evidence that cannot be handled.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok {
|
||||
panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
|
||||
}
|
||||
@ -109,6 +125,7 @@ func (k Keeper) HandleEquivocationEvidence(ctx sdk.Context, evidence *types.Equi
|
||||
consAddr,
|
||||
k.slashingKeeper.SlashFractionDoubleSign(ctx),
|
||||
evidence.GetValidatorPower(), distributionHeight,
|
||||
stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN,
|
||||
)
|
||||
|
||||
// Jail the validator if not already jailed. This will begin unbonding the
|
||||
|
||||
@ -157,15 +157,15 @@ func (mr *MockSlashingKeeperMockRecorder) JailUntil(arg0, arg1, arg2 interface{}
|
||||
}
|
||||
|
||||
// Slash mocks base method.
|
||||
func (m *MockSlashingKeeper) Slash(arg0 types0.Context, arg1 types0.ConsAddress, arg2 types0.Dec, arg3, arg4 int64) {
|
||||
func (m *MockSlashingKeeper) Slash(arg0 types0.Context, arg1 types0.ConsAddress, arg2 types0.Dec, arg3, arg4 int64, arg5 types2.Infraction) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4)
|
||||
m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// Slash indicates an expected call of Slash.
|
||||
func (mr *MockSlashingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||
func (mr *MockSlashingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockSlashingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockSlashingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// SlashFractionDoubleSign mocks base method.
|
||||
|
||||
@ -25,7 +25,7 @@ type (
|
||||
IsTombstoned(sdk.Context, sdk.ConsAddress) bool
|
||||
HasValidatorSigningInfo(sdk.Context, sdk.ConsAddress) bool
|
||||
Tombstone(sdk.Context, sdk.ConsAddress)
|
||||
Slash(sdk.Context, sdk.ConsAddress, sdk.Dec, int64, int64)
|
||||
Slash(sdk.Context, sdk.ConsAddress, sdk.Dec, int64, int64, stakingtypes.Infraction)
|
||||
SlashFractionDoubleSign(sdk.Context) sdk.Dec
|
||||
Jail(sdk.Context, sdk.ConsAddress)
|
||||
JailUntil(sdk.Context, sdk.ConsAddress, time.Time)
|
||||
|
||||
@ -283,7 +283,7 @@ for vote in block.LastCommitInfo.Votes {
|
||||
// That's fine since this is just used to filter unbonding delegations & redelegations.
|
||||
distributionHeight := height - sdk.ValidatorUpdateDelay - 1
|
||||
|
||||
Slash(vote.Validator.Address, distributionHeight, vote.Validator.Power, SlashFractionDowntime())
|
||||
Slash(vote.Validator.Address, distributionHeight, vote.Validator.Power, SlashFractionDowntime(), stakingtypes.Downtime)
|
||||
Jail(vote.Validator.Address)
|
||||
|
||||
signInfo.JailedUntil = block.Time.Add(DowntimeJailDuration())
|
||||
|
||||
@ -86,3 +86,7 @@ func (h Hooks) AfterDelegationModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.Va
|
||||
func (h Hooks) BeforeValidatorSlashed(_ sdk.Context, _ sdk.ValAddress, _ sdk.Dec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Hooks) AfterUnbondingInitiated(_ sdk.Context, _ uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
// HandleValidatorSignature handles a validator signature, must be called once per validator per block.
|
||||
@ -19,6 +20,11 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
|
||||
panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr))
|
||||
}
|
||||
|
||||
// don't update missed blocks when validator's jailed
|
||||
if k.sk.IsValidatorJailed(ctx, consAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
// fetch signing info
|
||||
signInfo, found := k.GetValidatorSigningInfo(ctx, consAddr)
|
||||
if !found {
|
||||
@ -84,7 +90,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
|
||||
// That's fine since this is just used to filter unbonding delegations & redelegations.
|
||||
distributionHeight := height - sdk.ValidatorUpdateDelay - 1
|
||||
|
||||
coinsBurned := k.sk.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx))
|
||||
coinsBurned := k.sk.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx), stakingtypes.Infraction_INFRACTION_DOWNTIME)
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeSlash,
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
// Keeper of the slashing store
|
||||
@ -70,8 +71,8 @@ func (k Keeper) GetPubkey(ctx sdk.Context, a cryptotypes.Address) (cryptotypes.P
|
||||
|
||||
// Slash attempts to slash a validator. The slash is delegated to the staking
|
||||
// module to make the necessary validator changes.
|
||||
func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, fraction sdk.Dec, power, distributionHeight int64) {
|
||||
coinsBurned := k.sk.Slash(ctx, consAddr, distributionHeight, power, fraction)
|
||||
func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, fraction sdk.Dec, power, distributionHeight int64, infraction stakingtypes.Infraction) {
|
||||
coinsBurned := k.sk.Slash(ctx, consAddr, distributionHeight, power, fraction, infraction)
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeSlash,
|
||||
|
||||
@ -281,6 +281,20 @@ func (mr *MockStakingKeeperMockRecorder) GetAllValidators(ctx interface{}) *gomo
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllValidators", reflect.TypeOf((*MockStakingKeeper)(nil).GetAllValidators), ctx)
|
||||
}
|
||||
|
||||
// IsValidatorJailed mocks base method.
|
||||
func (m *MockStakingKeeper) IsValidatorJailed(ctx types.Context, addr types.ConsAddress) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsValidatorJailed", ctx, addr)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsValidatorJailed indicates an expected call of IsValidatorJailed.
|
||||
func (mr *MockStakingKeeperMockRecorder) IsValidatorJailed(ctx, addr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsValidatorJailed", reflect.TypeOf((*MockStakingKeeper)(nil).IsValidatorJailed), ctx, addr)
|
||||
}
|
||||
|
||||
// IterateValidators mocks base method.
|
||||
func (m *MockStakingKeeper) IterateValidators(arg0 types.Context, arg1 func(int64, types2.ValidatorI) bool) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -320,17 +334,17 @@ func (mr *MockStakingKeeperMockRecorder) MaxValidators(arg0 interface{}) *gomock
|
||||
}
|
||||
|
||||
// Slash mocks base method.
|
||||
func (m *MockStakingKeeper) Slash(arg0 types.Context, arg1 types.ConsAddress, arg2, arg3 int64, arg4 types.Dec) math.Int {
|
||||
func (m *MockStakingKeeper) Slash(arg0 types.Context, arg1 types.ConsAddress, arg2, arg3 int64, arg4 types.Dec, arg5 types2.Infraction) math.Int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4)
|
||||
ret := m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret0, _ := ret[0].(math.Int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Slash indicates an expected call of Slash.
|
||||
func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||
func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockStakingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockStakingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// Unjail mocks base method.
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
// noalias
|
||||
// DONTCOVER
|
||||
package types
|
||||
|
||||
import (
|
||||
@ -43,7 +41,7 @@ type StakingKeeper interface {
|
||||
ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingtypes.ValidatorI // get a particular validator by consensus address
|
||||
|
||||
// slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction
|
||||
Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) math.Int
|
||||
Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, stakingtypes.Infraction) math.Int
|
||||
Jail(sdk.Context, sdk.ConsAddress) // jail a validator
|
||||
Unjail(sdk.Context, sdk.ConsAddress) // unjail a validator
|
||||
|
||||
@ -54,6 +52,9 @@ type StakingKeeper interface {
|
||||
|
||||
// MaxValidators returns the maximum amount of bonded validators
|
||||
MaxValidators(sdk.Context) uint32
|
||||
|
||||
// IsValidatorJailed returns if the validator is jailed.
|
||||
IsValidatorJailed(ctx sdk.Context, addr sdk.ConsAddress) bool
|
||||
}
|
||||
|
||||
// StakingHooks event hooks for staking validator object (noalias)
|
||||
|
||||
@ -73,6 +73,19 @@ Store entries prefixed with "Last" must remain unchanged until EndBlock.
|
||||
|
||||
* LastTotalPower: `0x12 -> ProtocolBuffer(math.Int)`
|
||||
|
||||
## ValidatorUpdates
|
||||
|
||||
ValidatorUpdates contains the validator updates returned to ABCI at the end of every block.
|
||||
The values are overwritten in every block.
|
||||
|
||||
* ValidatorUpdates `0x61 -> []abci.ValidatorUpdate`
|
||||
|
||||
## UnbondingID
|
||||
|
||||
UnbondingID stores the ID of the latest unbonding operation. It enables to create unique IDs for unbonding operation, i.e., UnbondingID is incremented every time a new unbonding operation (validator unbonding, unbonding delegation, redelegation) is initiated.
|
||||
|
||||
* UnbondingID: `0x37 -> uint64`
|
||||
|
||||
## Params
|
||||
|
||||
The staking module stores its params in state with the prefix of `0x51`,
|
||||
@ -111,12 +124,16 @@ records within a block.
|
||||
* ValidatorsByConsAddr: `0x22 | ConsAddrLen (1 byte) | ConsAddr -> OperatorAddr`
|
||||
* ValidatorsByPower: `0x23 | BigEndian(ConsensusPower) | OperatorAddrLen (1 byte) | OperatorAddr -> OperatorAddr`
|
||||
* LastValidatorsPower: `0x11 | OperatorAddrLen (1 byte) | OperatorAddr -> ProtocolBuffer(ConsensusPower)`
|
||||
* ValidatorsByUnbondingID: `0x38 | UnbondingID -> 0x21 | OperatorAddrLen (1 byte) | OperatorAddr`
|
||||
|
||||
`Validators` is the primary index - it ensures that each operator can have only one
|
||||
associated validator, where the public key of that validator can change in the
|
||||
future. Delegators can refer to the immutable operator of the validator, without
|
||||
concern for the changing public key.
|
||||
|
||||
`ValidatorsByUnbondingID` is an additional index that enables lookups for
|
||||
validators by the unbonding IDs corresponding to their current unbonding.
|
||||
|
||||
`ValidatorByConsAddr` is an additional index that enables lookups for slashing.
|
||||
When Tendermint reports evidence, it provides the validator address, so this
|
||||
map is needed to find the operator. Note that the `ConsAddr` corresponds to the
|
||||
@ -180,11 +197,18 @@ detected.
|
||||
|
||||
* UnbondingDelegation: `0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr -> ProtocolBuffer(unbondingDelegation)`
|
||||
* UnbondingDelegationsFromValidator: `0x33 | ValidatorAddrLen (1 byte) | ValidatorAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil`
|
||||
* UnbondingDelegationByUnbondingId: `0x38 | UnbondingId -> 0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr`
|
||||
`UnbondingDelegation` is used in queries, to lookup all unbonding delegations for
|
||||
a given delegator.
|
||||
|
||||
`UnbondingDelegationsFromValidator` is used in slashing, to lookup all
|
||||
unbonding delegations associated with a given validator that need to be
|
||||
slashed.
|
||||
|
||||
`UnbondingDelegationByUnbondingId` is an additional index that enables
|
||||
lookups for unbonding delegations by the unbonding IDs of the containing
|
||||
unbonding delegation entries.
|
||||
|
||||
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.
|
||||
|
||||
@ -203,11 +227,23 @@ committed by the source validator.
|
||||
* Redelegations: `0x34 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddr -> ProtocolBuffer(redelegation)`
|
||||
* RedelegationsBySrc: `0x35 | ValidatorSrcAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddrLen (1 byte) | ValidatorDstAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil`
|
||||
* RedelegationsByDst: `0x36 | ValidatorDstAddrLen (1 byte) | ValidatorDstAddr | ValidatorSrcAddrLen (1 byte) | ValidatorSrcAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil`
|
||||
* RedelegationByUnbondingId: `0x38 | UnbondingId -> 0x34 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddr`
|
||||
|
||||
`Redelegations` is used for queries, to lookup all redelegations for a given
|
||||
delegator.
|
||||
|
||||
`RedelegationsBySrc` is used for slashing based on the `ValidatorSrcAddr`.
|
||||
|
||||
`RedelegationsByDst` is used for slashing based on the `ValidatorDstAddr`
|
||||
|
||||
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 `ValidatorSrcAddr`,
|
||||
while the third map is for slashing based on the `ValidatorDstAddr`.
|
||||
|
||||
`RedelegationByUnbondingId` is an additional index that enables
|
||||
lookups for redelegations by the unbonding IDs of the containing
|
||||
redelegation entries.
|
||||
|
||||
A redelegation object is created every time a redelegation occurs. To prevent
|
||||
"redelegation hopping" redelegations may not occur under the situation that:
|
||||
|
||||
@ -355,13 +391,17 @@ As a part of the Undelegate and Complete Unbonding state transitions Unbond
|
||||
Delegation may be called.
|
||||
|
||||
* subtract the unbonded shares from delegator
|
||||
* add the unbonded tokens to an `UnbondingDelegation` Entry
|
||||
* add the unbonded tokens to an `UnbondingDelegationEntry`
|
||||
* update the delegation or remove the delegation if there are no more shares
|
||||
* if the delegation is the operator of the validator and no more shares exist then trigger a jail validator
|
||||
* update the validator with removed the delegator shares and associated coins
|
||||
* if the validator state is `Bonded`, transfer the `Coins` worth of the unbonded
|
||||
shares from the `BondedPool` to the `NotBondedPool` `ModuleAccount`
|
||||
* remove the validator if it is unbonded and there are no more delegation shares.
|
||||
* remove the validator if it is unbonded and there are no more delegation shares
|
||||
* get a unique `unbondingId` and map it to the `UnbondingDelegationEntry` in `UnbondingDelegationByUnbondingId`
|
||||
* call the `AfterUnbondingInitiated(unbondingId)` hook
|
||||
* add the unbonding delegation to `UnbondingDelegationQueue` with the completion time set to `UnbondingTime`
|
||||
|
||||
### Cancel an `UnbondingDelegation` Entry
|
||||
|
||||
@ -705,6 +745,12 @@ validators that still have remaining delegations, the `validator.Status` is
|
||||
switched from `types.Unbonding` to
|
||||
`types.Unbonded`.
|
||||
|
||||
Unbonding operations can be put on hold by external modules via the `PutUnbondingOnHold(unbondingId)` method.
|
||||
As a result, an unbonding operation (e.g., an unbonding delegation) that is on hold, cannot complete
|
||||
even if it reaches maturity. For an unbonding operation with `unbondingId` to eventually complete
|
||||
(after it reaches maturity), every call to `PutUnbondingOnHold(unbondingId)` must be matched
|
||||
by a call to `UnbondingCanComplete(unbondingId)`.
|
||||
|
||||
### Unbonding Delegations
|
||||
|
||||
Complete the unbonding of all mature `UnbondingDelegations.Entries` within the
|
||||
@ -749,6 +795,9 @@ following hooks can registered with staking:
|
||||
* called when a delegation is created or modified
|
||||
* `BeforeDelegationRemoved(Context, AccAddress, ValAddress) error`
|
||||
* called when a delegation is removed
|
||||
* `AfterUnbondingInitiated(Context, UnbondingID)`
|
||||
* called when an unbonding operation (validator unbonding, unbonding delegation, redelegation) was initiated
|
||||
|
||||
|
||||
# Events
|
||||
|
||||
|
||||
@ -271,17 +271,17 @@ func (k Keeper) HasMaxUnbondingDelegationEntries(ctx sdk.Context, delegatorAddr
|
||||
|
||||
// SetUnbondingDelegation sets the unbonding delegation and associated index.
|
||||
func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) {
|
||||
delegatorAddress := sdk.MustAccAddressFromBech32(ubd.DelegatorAddress)
|
||||
delAddr := sdk.MustAccAddressFromBech32(ubd.DelegatorAddress)
|
||||
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := types.MustMarshalUBD(k.cdc, ubd)
|
||||
addr, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress)
|
||||
valAddr, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
key := types.GetUBDKey(delegatorAddress, addr)
|
||||
key := types.GetUBDKey(delAddr, valAddr)
|
||||
store.Set(key, bz)
|
||||
store.Set(types.GetUBDByValIndexKey(delegatorAddress, addr), []byte{}) // index, store empty bytes
|
||||
store.Set(types.GetUBDByValIndexKey(delAddr, valAddr), []byte{}) // index, store empty bytes
|
||||
}
|
||||
|
||||
// RemoveUnbondingDelegation removes the unbonding delegation object and associated index.
|
||||
@ -305,14 +305,22 @@ func (k Keeper) SetUnbondingDelegationEntry(
|
||||
creationHeight int64, minTime time.Time, balance math.Int,
|
||||
) types.UnbondingDelegation {
|
||||
ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr)
|
||||
id := k.IncrementUnbondingID(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 UBDByUnbondingOp index to look up the UBD by the UBDE ID
|
||||
k.SetUnbondingDelegationByUnbondingID(ctx, ubd, id)
|
||||
|
||||
if err := k.Hooks().AfterUnbondingInitiated(ctx, id); err != nil {
|
||||
k.Logger(ctx).Error("failed to call after unbonding initiated hook", "error", err)
|
||||
}
|
||||
|
||||
return ubd
|
||||
}
|
||||
|
||||
@ -488,15 +496,23 @@ func (k Keeper) SetRedelegationEntry(ctx sdk.Context,
|
||||
sharesSrc, sharesDst sdk.Dec,
|
||||
) types.Redelegation {
|
||||
red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr)
|
||||
id := k.IncrementUnbondingID(ctx)
|
||||
if found {
|
||||
red.AddEntry(creationHeight, minTime, balance, sharesDst)
|
||||
red.AddEntry(creationHeight, minTime, balance, sharesDst, id)
|
||||
} else {
|
||||
red = types.NewRedelegation(delegatorAddr, validatorSrcAddr,
|
||||
validatorDstAddr, creationHeight, minTime, balance, sharesDst)
|
||||
validatorDstAddr, creationHeight, minTime, balance, sharesDst, id)
|
||||
}
|
||||
|
||||
k.SetRedelegation(ctx, red)
|
||||
|
||||
// Add to the UBDByEntry index to look up the UBD by the UBDE ID
|
||||
k.SetRedelegationByUnbondingID(ctx, red, id)
|
||||
|
||||
if err := k.Hooks().AfterUnbondingInitiated(ctx, id); err != nil {
|
||||
k.Logger(ctx).Error("failed to call after unbonding initiated hook", "error", err)
|
||||
}
|
||||
|
||||
return red
|
||||
}
|
||||
|
||||
@ -846,9 +862,10 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd
|
||||
// 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) {
|
||||
if entry.IsMature(ctxTime) && !entry.OnHold() {
|
||||
ubd.RemoveEntry(int64(i))
|
||||
i--
|
||||
k.DeleteUnbondingIndex(ctx, entry.UnbondingId)
|
||||
|
||||
// track undelegation only when remaining or truncated shares are non-zero
|
||||
if !entry.Balance.IsZero() {
|
||||
@ -950,9 +967,10 @@ func (k Keeper) CompleteRedelegation(
|
||||
// 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) {
|
||||
if entry.IsMature(ctxTime) && !entry.OnHold() {
|
||||
red.RemoveEntry(int64(i))
|
||||
i--
|
||||
k.DeleteUnbondingIndex(ctx, entry.UnbondingId)
|
||||
|
||||
if !entry.InitialBalance.IsZero() {
|
||||
balances = balances.Add(sdk.NewCoin(bondDenom, entry.InitialBalance))
|
||||
|
||||
@ -152,6 +152,7 @@ func (s *KeeperTestSuite) TestUnbondingDelegation() {
|
||||
0,
|
||||
time.Unix(0, 0).UTC(),
|
||||
sdk.NewInt(5),
|
||||
0,
|
||||
)
|
||||
|
||||
// set and retrieve a record
|
||||
@ -491,7 +492,7 @@ func (s *KeeperTestSuite) TestGetRedelegationsFromSrcValidator() {
|
||||
|
||||
rd := stakingtypes.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0,
|
||||
time.Unix(0, 0), sdk.NewInt(5),
|
||||
math.LegacyNewDec(5))
|
||||
math.LegacyNewDec(5), 0)
|
||||
|
||||
// set and retrieve a record
|
||||
keeper.SetRedelegation(ctx, rd)
|
||||
@ -518,7 +519,7 @@ func (s *KeeperTestSuite) TestRedelegation() {
|
||||
|
||||
rd := stakingtypes.NewRedelegation(addrDels[0], addrVals[0], addrVals[1], 0,
|
||||
time.Unix(0, 0).UTC(), sdk.NewInt(5),
|
||||
math.LegacyNewDec(5))
|
||||
math.LegacyNewDec(5), 0)
|
||||
|
||||
// test shouldn't have and redelegations
|
||||
has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1])
|
||||
|
||||
@ -597,6 +597,7 @@ func RedelegationsToRedelegationResponses(ctx sdk.Context, k *Keeper, redels typ
|
||||
entry.SharesDst,
|
||||
entry.InitialBalance,
|
||||
val.TokensFromShares(entry.SharesDst).TruncateInt(),
|
||||
entry.UnbondingId,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"cosmossdk.io/math"
|
||||
|
||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
@ -111,3 +112,21 @@ func (k Keeper) SetLastTotalPower(ctx sdk.Context, power math.Int) {
|
||||
func (k Keeper) GetAuthority() string {
|
||||
return k.authority
|
||||
}
|
||||
|
||||
// SetValidatorUpdates sets the ABCI validator power updates for the current block.
|
||||
func (k Keeper) SetValidatorUpdates(ctx sdk.Context, valUpdates []abci.ValidatorUpdate) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := k.cdc.MustMarshal(&types.ValidatorUpdates{Updates: valUpdates})
|
||||
store.Set(types.ValidatorUpdatesKey, bz)
|
||||
}
|
||||
|
||||
// GetValidatorUpdates returns the ABCI validator power updates within the current block.
|
||||
func (k Keeper) GetValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(types.ValidatorUpdatesKey)
|
||||
|
||||
var valUpdates types.ValidatorUpdates
|
||||
k.cdc.MustUnmarshal(bz, &valUpdates)
|
||||
|
||||
return valUpdates.Updates
|
||||
}
|
||||
|
||||
@ -27,12 +27,12 @@ func (k Keeper) MaxEntries(ctx sdk.Context) uint32 {
|
||||
|
||||
// HistoricalEntries = number of historical info entries
|
||||
// to persist in store
|
||||
func (k Keeper) HistoricalEntries(ctx sdk.Context) (res uint32) {
|
||||
func (k Keeper) HistoricalEntries(ctx sdk.Context) uint32 {
|
||||
return k.GetParams(ctx).HistoricalEntries
|
||||
}
|
||||
|
||||
// BondDenom - Bondable coin denomination
|
||||
func (k Keeper) BondDenom(ctx sdk.Context) (res string) {
|
||||
func (k Keeper) BondDenom(ctx sdk.Context) string {
|
||||
return k.GetParams(ctx).BondDenom
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,10 @@ import (
|
||||
//
|
||||
// Infraction was committed at the current height or at a past height,
|
||||
// not at a height in the future
|
||||
func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) math.Int {
|
||||
// ---
|
||||
//
|
||||
// Slash implementation doesn't require the infraction (types.Infraction) to work but the IS one does. It is here to have IS satisfy the Slash signature.
|
||||
func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec, _ types.Infraction) math.Int {
|
||||
logger := k.Logger(ctx)
|
||||
|
||||
if slashFactor.IsNegative() {
|
||||
@ -191,7 +194,7 @@ func (k Keeper) SlashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.IsMature(now) {
|
||||
if entry.IsMature(now) && !entry.OnHold() {
|
||||
// Unbonding delegation no longer eligible for slashing, skip it
|
||||
continue
|
||||
}
|
||||
@ -245,7 +248,7 @@ func (k Keeper) SlashRedelegation(ctx sdk.Context, srcValidator types.Validator,
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.IsMature(now) {
|
||||
if entry.IsMature(now) && !entry.OnHold() {
|
||||
// Redelegation no longer eligible for slashing, skip it
|
||||
continue
|
||||
}
|
||||
|
||||
@ -46,5 +46,5 @@ func (s *KeeperTestSuite) TestSlashAtFutureHeight() {
|
||||
require.NoError(err)
|
||||
|
||||
fraction := sdk.NewDecWithPrec(5, 1)
|
||||
require.Panics(func() { keeper.Slash(ctx, consAddr, 1, 10, fraction) })
|
||||
require.Panics(func() { keeper.Slash(ctx, consAddr, 1, 10, fraction, 0) })
|
||||
}
|
||||
|
||||
428
x/staking/keeper/unbonding.go
Normal file
428
x/staking/keeper/unbonding.go
Normal file
@ -0,0 +1,428 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
// Increments and returns a unique ID for an unbonding operation
|
||||
func (k Keeper) IncrementUnbondingID(ctx sdk.Context) (unbondingID uint64) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(types.UnbondingIDKey)
|
||||
if bz != nil {
|
||||
unbondingID = binary.BigEndian.Uint64(bz)
|
||||
}
|
||||
|
||||
unbondingID++
|
||||
|
||||
// Convert back into bytes for storage
|
||||
bz = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bz, unbondingID)
|
||||
|
||||
store.Set(types.UnbondingIDKey, bz)
|
||||
|
||||
return unbondingID
|
||||
}
|
||||
|
||||
// Remove a mapping from UnbondingId to unbonding operation
|
||||
func (k Keeper) DeleteUnbondingIndex(ctx sdk.Context, id uint64) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store.Delete(types.GetUnbondingIndexKey(id))
|
||||
}
|
||||
|
||||
func (k Keeper) GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType types.UnbondingType, found bool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
bz := store.Get(types.GetUnbondingTypeKey(id))
|
||||
if bz == nil {
|
||||
return unbondingType, false
|
||||
}
|
||||
|
||||
return types.UnbondingType(binary.BigEndian.Uint64(bz)), true
|
||||
}
|
||||
|
||||
func (k Keeper) SetUnbondingType(ctx sdk.Context, id uint64, unbondingType types.UnbondingType) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
// Convert into bytes for storage
|
||||
bz := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bz, uint64(unbondingType))
|
||||
|
||||
store.Set(types.GetUnbondingTypeKey(id), bz)
|
||||
}
|
||||
|
||||
// GetUnbondingDelegationByUnbondingID returns a unbonding delegation that has an unbonding delegation entry with a certain ID
|
||||
func (k Keeper) GetUnbondingDelegationByUnbondingID(ctx sdk.Context, id uint64) (ubd types.UnbondingDelegation, found bool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
ubdKey := store.Get(types.GetUnbondingIndexKey(id))
|
||||
if ubdKey == nil {
|
||||
return types.UnbondingDelegation{}, false
|
||||
}
|
||||
|
||||
value := store.Get(ubdKey)
|
||||
if value == nil {
|
||||
return types.UnbondingDelegation{}, false
|
||||
}
|
||||
|
||||
ubd, err := types.UnmarshalUBD(k.cdc, value)
|
||||
// An error here means that what we got wasn't the right type
|
||||
if err != nil {
|
||||
return types.UnbondingDelegation{}, false
|
||||
}
|
||||
|
||||
return ubd, true
|
||||
}
|
||||
|
||||
// GetRedelegationByUnbondingID returns a unbonding delegation that has an unbonding delegation entry with a certain ID
|
||||
func (k Keeper) GetRedelegationByUnbondingID(ctx sdk.Context, id uint64) (red types.Redelegation, found bool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
redKey := store.Get(types.GetUnbondingIndexKey(id))
|
||||
if redKey == nil {
|
||||
return types.Redelegation{}, false
|
||||
}
|
||||
|
||||
value := store.Get(redKey)
|
||||
if value == nil {
|
||||
return types.Redelegation{}, false
|
||||
}
|
||||
|
||||
red, err := types.UnmarshalRED(k.cdc, value)
|
||||
// An error here means that what we got wasn't the right type
|
||||
if err != nil {
|
||||
return types.Redelegation{}, false
|
||||
}
|
||||
|
||||
return red, true
|
||||
}
|
||||
|
||||
// GetValidatorByUnbondingID returns the validator that is unbonding with a certain unbonding op ID
|
||||
func (k Keeper) GetValidatorByUnbondingID(ctx sdk.Context, id uint64) (val types.Validator, found bool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
valKey := store.Get(types.GetUnbondingIndexKey(id))
|
||||
if valKey == nil {
|
||||
return types.Validator{}, false
|
||||
}
|
||||
|
||||
value := store.Get(valKey)
|
||||
if value == nil {
|
||||
return types.Validator{}, false
|
||||
}
|
||||
|
||||
val, err := types.UnmarshalValidator(k.cdc, value)
|
||||
// An error here means that what we got wasn't the right type
|
||||
if err != nil {
|
||||
return types.Validator{}, false
|
||||
}
|
||||
|
||||
return val, true
|
||||
}
|
||||
|
||||
// SetUnbondingDelegationByUnbondingID sets an index to look up an UnbondingDelegation by the unbondingID of an UnbondingDelegationEntry that it contains
|
||||
// Note, it does not set the unbonding delegation itself, use SetUnbondingDelegation(ctx, ubd) for that
|
||||
func (k Keeper) SetUnbondingDelegationByUnbondingID(ctx sdk.Context, ubd types.UnbondingDelegation, id uint64) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
delAddr := sdk.MustAccAddressFromBech32(ubd.DelegatorAddress)
|
||||
valAddr, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ubdKey := types.GetUBDKey(delAddr, valAddr)
|
||||
store.Set(types.GetUnbondingIndexKey(id), ubdKey)
|
||||
|
||||
// Set unbonding type so that we know how to deserialize it later
|
||||
k.SetUnbondingType(ctx, id, types.UnbondingType_UnbondingDelegation)
|
||||
}
|
||||
|
||||
// SetRedelegationByUnbondingID sets an index to look up an Redelegation by the unbondingID of an RedelegationEntry that it contains
|
||||
// Note, it does not set the redelegation itself, use SetRedelegation(ctx, red) for that
|
||||
func (k Keeper) SetRedelegationByUnbondingID(ctx sdk.Context, red types.Redelegation, id uint64) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
delAddr := sdk.MustAccAddressFromBech32(red.DelegatorAddress)
|
||||
|
||||
valSrcAddr, err := sdk.ValAddressFromBech32(red.ValidatorSrcAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
valDstAddr, err := sdk.ValAddressFromBech32(red.ValidatorDstAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
redKey := types.GetREDKey(delAddr, valSrcAddr, valDstAddr)
|
||||
store.Set(types.GetUnbondingIndexKey(id), redKey)
|
||||
|
||||
// Set unbonding type so that we know how to deserialize it later
|
||||
k.SetUnbondingType(ctx, id, types.UnbondingType_Redelegation)
|
||||
}
|
||||
|
||||
// SetValidatorByUnbondingID sets an index to look up a Validator by the unbondingID corresponding to its current unbonding
|
||||
// Note, it does not set the validator itself, use SetValidator(ctx, val) for that
|
||||
func (k Keeper) SetValidatorByUnbondingID(ctx sdk.Context, val types.Validator, id uint64) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
valKey := types.GetValidatorKey(valAddr)
|
||||
store.Set(types.GetUnbondingIndexKey(id), valKey)
|
||||
|
||||
// Set unbonding type so that we know how to deserialize it later
|
||||
k.SetUnbondingType(ctx, id, types.UnbondingType_ValidatorUnbonding)
|
||||
}
|
||||
|
||||
// unbondingDelegationEntryArrayIndex and redelegationEntryArrayIndex are utilities to find
|
||||
// at which position in the Entries array the entry with a given id is
|
||||
func unbondingDelegationEntryArrayIndex(ubd types.UnbondingDelegation, id uint64) (index int, found bool) {
|
||||
for i, entry := range ubd.Entries {
|
||||
// we find the entry with the right ID
|
||||
if entry.UnbondingId == id {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func redelegationEntryArrayIndex(red types.Redelegation, id uint64) (index int, found bool) {
|
||||
for i, entry := range red.Entries {
|
||||
// we find the entry with the right ID
|
||||
if entry.UnbondingId == id {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// UnbondingCanComplete allows a stopped unbonding operation, such as an
|
||||
// unbonding delegation, a redelegation, or a validator unbonding to complete.
|
||||
// In order for the unbonding operation with `id` to eventually complete, every call
|
||||
// to PutUnbondingOnHold(id) must be matched by a call to UnbondingCanComplete(id).
|
||||
func (k Keeper) UnbondingCanComplete(ctx sdk.Context, id uint64) error {
|
||||
unbondingType, found := k.GetUnbondingType(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
switch unbondingType {
|
||||
case types.UnbondingType_UnbondingDelegation:
|
||||
if err := k.unbondingDelegationEntryCanComplete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.UnbondingType_Redelegation:
|
||||
if err := k.redelegationEntryCanComplete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.UnbondingType_ValidatorUnbonding:
|
||||
if err := k.validatorUnbondingCanComplete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) unbondingDelegationEntryCanComplete(ctx sdk.Context, id uint64) error {
|
||||
ubd, found := k.GetUnbondingDelegationByUnbondingID(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
i, found := unbondingDelegationEntryArrayIndex(ubd, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
// The entry must be on hold
|
||||
if !ubd.Entries[i].OnHold() {
|
||||
return sdkerrors.Wrapf(
|
||||
types.ErrUnbondingOnHoldRefCountNegative,
|
||||
"undelegation unbondingID(%d), expecting UnbondingOnHoldRefCount > 0, got %T",
|
||||
id, ubd.Entries[i].UnbondingOnHoldRefCount,
|
||||
)
|
||||
}
|
||||
ubd.Entries[i].UnbondingOnHoldRefCount--
|
||||
|
||||
// Check if entry is matured.
|
||||
if !ubd.Entries[i].OnHold() && ubd.Entries[i].IsMature(ctx.BlockHeader().Time) {
|
||||
// If matured, complete it.
|
||||
delegatorAddress, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bondDenom := k.GetParams(ctx).BondDenom
|
||||
|
||||
// track undelegation only when remaining or truncated shares are non-zero
|
||||
if !ubd.Entries[i].Balance.IsZero() {
|
||||
amt := sdk.NewCoin(bondDenom, ubd.Entries[i].Balance)
|
||||
if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(
|
||||
ctx, types.NotBondedPoolName, delegatorAddress, sdk.NewCoins(amt),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entry
|
||||
ubd.RemoveEntry(int64(i))
|
||||
// Remove from the UnbondingIndex
|
||||
k.DeleteUnbondingIndex(ctx, id)
|
||||
}
|
||||
|
||||
// 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 nil
|
||||
}
|
||||
|
||||
func (k Keeper) redelegationEntryCanComplete(ctx sdk.Context, id uint64) error {
|
||||
red, found := k.GetRedelegationByUnbondingID(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
i, found := redelegationEntryArrayIndex(red, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
// The entry must be on hold
|
||||
if !red.Entries[i].OnHold() {
|
||||
return sdkerrors.Wrapf(
|
||||
types.ErrUnbondingOnHoldRefCountNegative,
|
||||
"redelegation unbondingID(%d), expecting UnbondingOnHoldRefCount > 0, got %T",
|
||||
id, red.Entries[i].UnbondingOnHoldRefCount,
|
||||
)
|
||||
}
|
||||
red.Entries[i].UnbondingOnHoldRefCount--
|
||||
|
||||
if !red.Entries[i].OnHold() && red.Entries[i].IsMature(ctx.BlockHeader().Time) {
|
||||
// If matured, complete it.
|
||||
// Remove entry
|
||||
red.RemoveEntry(int64(i))
|
||||
// Remove from the Unbonding index
|
||||
k.DeleteUnbondingIndex(ctx, id)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Successfully completed unbonding
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) validatorUnbondingCanComplete(ctx sdk.Context, id uint64) error {
|
||||
val, found := k.GetValidatorByUnbondingID(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
if val.UnbondingOnHoldRefCount <= 0 {
|
||||
return sdkerrors.Wrapf(
|
||||
types.ErrUnbondingOnHoldRefCountNegative,
|
||||
"val(%s), expecting UnbondingOnHoldRefCount > 0, got %T",
|
||||
val.OperatorAddress, val.UnbondingOnHoldRefCount,
|
||||
)
|
||||
}
|
||||
val.UnbondingOnHoldRefCount--
|
||||
k.SetValidator(ctx, val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutUnbondingOnHold allows an external module to stop an unbonding operation,
|
||||
// such as an unbonding delegation, a redelegation, or a validator unbonding.
|
||||
// In order for the unbonding operation with `id` to eventually complete, every call
|
||||
// to PutUnbondingOnHold(id) must be matched by a call to UnbondingCanComplete(id).
|
||||
func (k Keeper) PutUnbondingOnHold(ctx sdk.Context, id uint64) error {
|
||||
unbondingType, found := k.GetUnbondingType(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
switch unbondingType {
|
||||
case types.UnbondingType_UnbondingDelegation:
|
||||
if err := k.putUnbondingDelegationEntryOnHold(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.UnbondingType_Redelegation:
|
||||
if err := k.putRedelegationEntryOnHold(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.UnbondingType_ValidatorUnbonding:
|
||||
if err := k.putValidatorOnHold(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) putUnbondingDelegationEntryOnHold(ctx sdk.Context, id uint64) error {
|
||||
ubd, found := k.GetUnbondingDelegationByUnbondingID(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
i, found := unbondingDelegationEntryArrayIndex(ubd, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
ubd.Entries[i].UnbondingOnHoldRefCount++
|
||||
k.SetUnbondingDelegation(ctx, ubd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) putRedelegationEntryOnHold(ctx sdk.Context, id uint64) error {
|
||||
red, found := k.GetRedelegationByUnbondingID(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
i, found := redelegationEntryArrayIndex(red, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
red.Entries[i].UnbondingOnHoldRefCount++
|
||||
k.SetRedelegation(ctx, red)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) putValidatorOnHold(ctx sdk.Context, id uint64) error {
|
||||
val, found := k.GetValidatorByUnbondingID(ctx, id)
|
||||
if !found {
|
||||
return types.ErrUnbondingNotFound
|
||||
}
|
||||
|
||||
val.UnbondingOnHoldRefCount++
|
||||
k.SetValidator(ctx, val)
|
||||
|
||||
return nil
|
||||
}
|
||||
332
x/staking/keeper/unbonding_test.go
Normal file
332
x/staking/keeper/unbonding_test.go
Normal file
@ -0,0 +1,332 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/testutil"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
func (s *KeeperTestSuite) TestIncrementUnbondingID() {
|
||||
for i := 1; i < 10; i++ {
|
||||
s.Require().Equal(uint64(i), s.stakingKeeper.IncrementUnbondingID(s.ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KeeperTestSuite) TestUnbondingTypeAccessors() {
|
||||
cases := []struct {
|
||||
exists bool
|
||||
name string
|
||||
expected types.UnbondingType
|
||||
}{
|
||||
{
|
||||
name: "existing 1",
|
||||
exists: true,
|
||||
expected: types.UnbondingType_UnbondingDelegation,
|
||||
},
|
||||
{
|
||||
name: "existing 2",
|
||||
exists: true,
|
||||
expected: types.UnbondingType_Redelegation,
|
||||
},
|
||||
{
|
||||
name: "not existing",
|
||||
exists: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
s.Run(tc.name, func() {
|
||||
if tc.exists {
|
||||
s.stakingKeeper.SetUnbondingType(s.ctx, uint64(i), tc.expected)
|
||||
}
|
||||
|
||||
unbondingType, found := s.stakingKeeper.GetUnbondingType(s.ctx, uint64(i))
|
||||
if tc.exists {
|
||||
s.Require().True(found)
|
||||
s.Require().Equal(tc.expected, unbondingType)
|
||||
} else {
|
||||
s.Require().False(found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KeeperTestSuite) TestUnbondingDelegationByUnbondingIDAccessors() {
|
||||
delAddrs, valAddrs := createValAddrs(2)
|
||||
|
||||
type exists struct {
|
||||
setUnbondingDelegation bool
|
||||
setUnbondingDelegationByUnbondingID bool
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
exists exists
|
||||
name string
|
||||
expected types.UnbondingDelegation
|
||||
}{
|
||||
{
|
||||
name: "existing 1",
|
||||
exists: exists{true, true},
|
||||
expected: types.NewUnbondingDelegation(
|
||||
delAddrs[0],
|
||||
valAddrs[0],
|
||||
0,
|
||||
time.Unix(0, 0).UTC(),
|
||||
sdk.NewInt(5),
|
||||
0,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "not existing 1",
|
||||
exists: exists{false, true},
|
||||
expected: types.NewUnbondingDelegation(
|
||||
delAddrs[1],
|
||||
valAddrs[1],
|
||||
0,
|
||||
time.Unix(0, 0).UTC(),
|
||||
sdk.NewInt(5),
|
||||
0,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "not existing 2",
|
||||
exists: exists{false, false},
|
||||
expected: types.NewUnbondingDelegation(
|
||||
delAddrs[0],
|
||||
valAddrs[0],
|
||||
0,
|
||||
time.Unix(0, 0).UTC(),
|
||||
sdk.NewInt(5),
|
||||
0,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
s.Run(tc.name, func() {
|
||||
if tc.exists.setUnbondingDelegation {
|
||||
s.stakingKeeper.SetUnbondingDelegation(s.ctx, tc.expected)
|
||||
}
|
||||
|
||||
if tc.exists.setUnbondingDelegationByUnbondingID {
|
||||
s.stakingKeeper.SetUnbondingDelegationByUnbondingID(s.ctx, tc.expected, uint64(i))
|
||||
}
|
||||
|
||||
ubd, found := s.stakingKeeper.GetUnbondingDelegationByUnbondingID(s.ctx, uint64(i))
|
||||
if tc.exists.setUnbondingDelegation && tc.exists.setUnbondingDelegationByUnbondingID {
|
||||
s.Require().True(found)
|
||||
s.Require().Equal(tc.expected, ubd)
|
||||
} else {
|
||||
s.Require().False(found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KeeperTestSuite) TestRedelegationByUnbondingIDAccessors() {
|
||||
delAddrs, valAddrs := createValAddrs(2)
|
||||
|
||||
type exists struct {
|
||||
setRedelegation bool
|
||||
setRedelegationByUnbondingID bool
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
exists exists
|
||||
name string
|
||||
expected types.Redelegation
|
||||
}{
|
||||
{
|
||||
name: "existing 1",
|
||||
exists: exists{true, true},
|
||||
expected: types.NewRedelegation(
|
||||
delAddrs[0],
|
||||
valAddrs[0],
|
||||
valAddrs[1],
|
||||
0,
|
||||
time.Unix(5, 0).UTC(),
|
||||
sdk.NewInt(10),
|
||||
math.LegacyNewDec(10),
|
||||
0,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "not existing 1",
|
||||
exists: exists{false, true},
|
||||
expected: types.NewRedelegation(
|
||||
delAddrs[1],
|
||||
valAddrs[0],
|
||||
valAddrs[1],
|
||||
0,
|
||||
time.Unix(5, 0).UTC(),
|
||||
sdk.NewInt(10),
|
||||
math.LegacyNewDec(10),
|
||||
0,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "not existing 2",
|
||||
exists: exists{false, false},
|
||||
expected: types.NewRedelegation(
|
||||
delAddrs[1],
|
||||
valAddrs[1],
|
||||
valAddrs[0],
|
||||
0,
|
||||
time.Unix(5, 0).UTC(),
|
||||
sdk.NewInt(10),
|
||||
math.LegacyNewDec(10),
|
||||
0,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
s.Run(tc.name, func() {
|
||||
if tc.exists.setRedelegation {
|
||||
s.stakingKeeper.SetRedelegation(s.ctx, tc.expected)
|
||||
}
|
||||
|
||||
if tc.exists.setRedelegationByUnbondingID {
|
||||
s.stakingKeeper.SetRedelegationByUnbondingID(s.ctx, tc.expected, uint64(i))
|
||||
}
|
||||
|
||||
red, found := s.stakingKeeper.GetRedelegationByUnbondingID(s.ctx, uint64(i))
|
||||
if tc.exists.setRedelegation && tc.exists.setRedelegationByUnbondingID {
|
||||
s.Require().True(found)
|
||||
s.Require().Equal(tc.expected, red)
|
||||
} else {
|
||||
s.Require().False(found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KeeperTestSuite) TestValidatorByUnbondingIDAccessors() {
|
||||
_, valAddrs := createValAddrs(3)
|
||||
|
||||
type exists struct {
|
||||
setValidator bool
|
||||
setValidatorByUnbondingID bool
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
exists exists
|
||||
name string
|
||||
validator types.Validator
|
||||
}{
|
||||
{
|
||||
name: "existing 1",
|
||||
exists: exists{true, true},
|
||||
validator: testutil.NewValidator(s.T(), valAddrs[0], PKs[0]),
|
||||
},
|
||||
{
|
||||
name: "not existing 1",
|
||||
exists: exists{false, true},
|
||||
validator: testutil.NewValidator(s.T(), valAddrs[1], PKs[1]),
|
||||
},
|
||||
{
|
||||
name: "not existing 2",
|
||||
exists: exists{false, false},
|
||||
validator: testutil.NewValidator(s.T(), valAddrs[2], PKs[0]),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
s.Run(tc.name, func() {
|
||||
if tc.exists.setValidator {
|
||||
s.stakingKeeper.SetValidator(s.ctx, tc.validator)
|
||||
}
|
||||
|
||||
if tc.exists.setValidatorByUnbondingID {
|
||||
s.stakingKeeper.SetValidatorByUnbondingID(s.ctx, tc.validator, uint64(i))
|
||||
}
|
||||
|
||||
val, found := s.stakingKeeper.GetValidatorByUnbondingID(s.ctx, uint64(i))
|
||||
if tc.exists.setValidator && tc.exists.setValidatorByUnbondingID {
|
||||
s.Require().True(found)
|
||||
s.Require().Equal(tc.validator, val)
|
||||
} else {
|
||||
s.Require().False(found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KeeperTestSuite) TestUnbondingCanComplete() {
|
||||
delAddrs, valAddrs := createValAddrs(3)
|
||||
unbondingID := uint64(1)
|
||||
|
||||
// no unbondingID set
|
||||
err := s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingNotFound)
|
||||
|
||||
// unbonding delegation
|
||||
s.stakingKeeper.SetUnbondingType(s.ctx, unbondingID, types.UnbondingType_UnbondingDelegation)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingNotFound)
|
||||
|
||||
ubd := types.NewUnbondingDelegation(
|
||||
delAddrs[0],
|
||||
valAddrs[0],
|
||||
0,
|
||||
time.Unix(0, 0).UTC(),
|
||||
sdk.NewInt(5),
|
||||
unbondingID,
|
||||
)
|
||||
s.stakingKeeper.SetUnbondingDelegation(s.ctx, ubd)
|
||||
s.stakingKeeper.SetUnbondingDelegationByUnbondingID(s.ctx, ubd, unbondingID)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingOnHoldRefCountNegative)
|
||||
|
||||
err = s.stakingKeeper.PutUnbondingOnHold(s.ctx, unbondingID)
|
||||
s.Require().NoError(err)
|
||||
s.bankKeeper.EXPECT().UndelegateCoinsFromModuleToAccount(s.ctx, types.NotBondedPoolName, delAddrs[0], sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(5)))).Return(nil)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// redelegation
|
||||
unbondingID++
|
||||
s.stakingKeeper.SetUnbondingType(s.ctx, unbondingID, types.UnbondingType_Redelegation)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingNotFound)
|
||||
|
||||
red := types.NewRedelegation(
|
||||
delAddrs[0],
|
||||
valAddrs[0],
|
||||
valAddrs[1],
|
||||
0,
|
||||
time.Unix(5, 0).UTC(),
|
||||
sdk.NewInt(10),
|
||||
math.LegacyNewDec(10),
|
||||
unbondingID,
|
||||
)
|
||||
s.stakingKeeper.SetRedelegation(s.ctx, red)
|
||||
s.stakingKeeper.SetRedelegationByUnbondingID(s.ctx, red, unbondingID)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingOnHoldRefCountNegative)
|
||||
|
||||
err = s.stakingKeeper.PutUnbondingOnHold(s.ctx, unbondingID)
|
||||
s.Require().NoError(err)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// validator unbonding
|
||||
unbondingID++
|
||||
s.stakingKeeper.SetUnbondingType(s.ctx, unbondingID, types.UnbondingType_ValidatorUnbonding)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingNotFound)
|
||||
|
||||
val := testutil.NewValidator(s.T(), valAddrs[0], PKs[0])
|
||||
s.stakingKeeper.SetValidator(s.ctx, val)
|
||||
s.stakingKeeper.SetValidatorByUnbondingID(s.ctx, val, unbondingID)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().ErrorIs(err, types.ErrUnbondingOnHoldRefCountNegative)
|
||||
|
||||
err = s.stakingKeeper.PutUnbondingOnHold(s.ctx, unbondingID)
|
||||
s.Require().NoError(err)
|
||||
err = s.stakingKeeper.UnbondingCanComplete(s.ctx, unbondingID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
@ -219,6 +219,9 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab
|
||||
k.SetLastTotalPower(ctx, totalPower)
|
||||
}
|
||||
|
||||
// set the list of validator updates
|
||||
k.SetValidatorUpdates(ctx, updates)
|
||||
|
||||
return updates, err
|
||||
}
|
||||
|
||||
@ -229,7 +232,7 @@ func (k Keeper) bondedToUnbonding(ctx sdk.Context, validator types.Validator) (t
|
||||
panic(fmt.Sprintf("bad state transition bondedToUnbonding, validator: %v\n", validator))
|
||||
}
|
||||
|
||||
return k.beginUnbondingValidator(ctx, validator)
|
||||
return k.BeginUnbondingValidator(ctx, validator)
|
||||
}
|
||||
|
||||
func (k Keeper) unbondingToBonded(ctx sdk.Context, validator types.Validator) (types.Validator, error) {
|
||||
@ -307,7 +310,7 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) (types
|
||||
}
|
||||
|
||||
// perform all the store operations for when a validator begins unbonding
|
||||
func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validator) (types.Validator, error) {
|
||||
func (k Keeper) BeginUnbondingValidator(ctx sdk.Context, validator types.Validator) (types.Validator, error) {
|
||||
params := k.GetParams(ctx)
|
||||
|
||||
// delete the validator by power index, as the key will change
|
||||
@ -318,12 +321,16 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat
|
||||
panic(fmt.Sprintf("should not already be unbonded or unbonding, validator: %v\n", validator))
|
||||
}
|
||||
|
||||
id := k.IncrementUnbondingID(ctx)
|
||||
|
||||
validator = validator.UpdateStatus(types.Unbonding)
|
||||
|
||||
// set the unbonding completion time and completion height appropriately
|
||||
validator.UnbondingTime = ctx.BlockHeader().Time.Add(params.UnbondingTime)
|
||||
validator.UnbondingHeight = ctx.BlockHeader().Height
|
||||
|
||||
validator.UnbondingIds = append(validator.UnbondingIds, id)
|
||||
|
||||
// save the now unbonded validator record and power index
|
||||
k.SetValidator(ctx, validator)
|
||||
k.SetValidatorByPowerIndex(ctx, validator)
|
||||
@ -341,6 +348,12 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat
|
||||
return validator, err
|
||||
}
|
||||
|
||||
k.SetValidatorByUnbondingID(ctx, validator, id)
|
||||
|
||||
if err := k.Hooks().AfterUnbondingInitiated(ctx, id); err != nil {
|
||||
return validator, err
|
||||
}
|
||||
|
||||
return validator, nil
|
||||
}
|
||||
|
||||
|
||||
@ -182,7 +182,6 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) {
|
||||
store.Delete(types.GetValidatorByConsAddrKey(valConsAddr))
|
||||
store.Delete(types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx)))
|
||||
|
||||
// call hooks
|
||||
if err := k.Hooks().AfterValidatorRemoved(ctx, valConsAddr, validator.GetOperator()); err != nil {
|
||||
k.Logger(ctx).Error("error in after validator removed hook", "error", err)
|
||||
}
|
||||
@ -417,8 +416,6 @@ func (k Keeper) ValidatorQueueIterator(ctx sdk.Context, endTime time.Time, endHe
|
||||
// UnbondAllMatureValidators unbonds all the mature unbonding validators that
|
||||
// have finished their unbonding period.
|
||||
func (k Keeper) UnbondAllMatureValidators(ctx sdk.Context) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
|
||||
blockTime := ctx.BlockTime()
|
||||
blockHeight := ctx.BlockHeight()
|
||||
|
||||
@ -458,13 +455,33 @@ func (k Keeper) UnbondAllMatureValidators(ctx sdk.Context) {
|
||||
panic("unexpected validator in unbonding queue; status was not unbonding")
|
||||
}
|
||||
|
||||
val = k.UnbondingToUnbonded(ctx, val)
|
||||
if val.GetDelegatorShares().IsZero() {
|
||||
k.RemoveValidator(ctx, val.GetOperator())
|
||||
if val.UnbondingOnHoldRefCount == 0 {
|
||||
for _, id := range val.UnbondingIds {
|
||||
k.DeleteUnbondingIndex(ctx, id)
|
||||
}
|
||||
|
||||
val = k.UnbondingToUnbonded(ctx, val)
|
||||
|
||||
if val.GetDelegatorShares().IsZero() {
|
||||
k.RemoveValidator(ctx, val.GetOperator())
|
||||
} else {
|
||||
// remove unbonding ids
|
||||
val.UnbondingIds = []uint64{}
|
||||
}
|
||||
|
||||
// remove validator from queue
|
||||
k.DeleteValidatorQueue(ctx, val)
|
||||
}
|
||||
}
|
||||
|
||||
store.Delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keeper) IsValidatorJailed(ctx sdk.Context, addr sdk.ConsAddress) bool {
|
||||
v, ok := k.GetValidatorByConsAddr(ctx, addr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Jailed
|
||||
}
|
||||
|
||||
@ -40,8 +40,8 @@ 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, math.LegacyOneDec())
|
||||
ubd := types.NewUnbondingDelegation(delAddr1, valAddr1, 15, bondTime, math.OneInt())
|
||||
red := types.NewRedelegation(delAddr1, valAddr1, valAddr1, 12, bondTime, math.OneInt(), math.LegacyOneDec())
|
||||
ubd := types.NewUnbondingDelegation(delAddr1, valAddr1, 15, bondTime, math.OneInt(), 1)
|
||||
red := types.NewRedelegation(delAddr1, valAddr1, valAddr1, 12, bondTime, math.OneInt(), math.LegacyOneDec(), 0)
|
||||
|
||||
kvPairs := kv.Pairs{
|
||||
Pairs: []kv.Pair{
|
||||
|
||||
@ -195,7 +195,7 @@ func (s *SimTestSuite) TestSimulateMsgCancelUnbondingDelegation() {
|
||||
s.setupValidatorRewards(ctx, validator0.GetOperator())
|
||||
|
||||
// unbonding delegation
|
||||
udb := types.NewUnbondingDelegation(delegator.Address, validator0.GetOperator(), s.app.LastBlockHeight(), blockTime.Add(2*time.Minute), delTokens)
|
||||
udb := types.NewUnbondingDelegation(delegator.Address, validator0.GetOperator(), s.app.LastBlockHeight(), blockTime.Add(2*time.Minute), delTokens, 0)
|
||||
s.stakingKeeper.SetUnbondingDelegation(ctx, udb)
|
||||
s.setupValidatorRewards(ctx, validator0.GetOperator())
|
||||
|
||||
|
||||
@ -403,17 +403,17 @@ func (mr *MockValidatorSetMockRecorder) MaxValidators(arg0 interface{}) *gomock.
|
||||
}
|
||||
|
||||
// Slash mocks base method.
|
||||
func (m *MockValidatorSet) Slash(arg0 types.Context, arg1 types.ConsAddress, arg2, arg3 int64, arg4 types.Dec) math.Int {
|
||||
func (m *MockValidatorSet) Slash(arg0 types.Context, arg1 types.ConsAddress, arg2, arg3 int64, arg4 types.Dec, arg5 types1.Infraction) math.Int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4)
|
||||
ret := m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret0, _ := ret[0].(math.Int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Slash indicates an expected call of Slash.
|
||||
func (mr *MockValidatorSetMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||
func (mr *MockValidatorSetMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockValidatorSet)(nil).Slash), arg0, arg1, arg2, arg3, arg4)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockValidatorSet)(nil).Slash), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// StakingTokenSupply mocks base method.
|
||||
@ -570,6 +570,20 @@ func (mr *MockStakingHooksMockRecorder) AfterDelegationModified(ctx, delAddr, va
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AfterDelegationModified", reflect.TypeOf((*MockStakingHooks)(nil).AfterDelegationModified), ctx, delAddr, valAddr)
|
||||
}
|
||||
|
||||
// AfterUnbondingInitiated mocks base method.
|
||||
func (m *MockStakingHooks) AfterUnbondingInitiated(ctx types.Context, id uint64) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AfterUnbondingInitiated", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AfterUnbondingInitiated indicates an expected call of AfterUnbondingInitiated.
|
||||
func (mr *MockStakingHooksMockRecorder) AfterUnbondingInitiated(ctx, id interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AfterUnbondingInitiated", reflect.TypeOf((*MockStakingHooks)(nil).AfterUnbondingInitiated), ctx, id)
|
||||
}
|
||||
|
||||
// AfterValidatorBeginUnbonding mocks base method.
|
||||
func (m *MockStakingHooks) AfterValidatorBeginUnbonding(ctx types.Context, consAddr types.ConsAddress, valAddr types.ValAddress) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@ -92,12 +92,14 @@ func (d Delegations) String() (out string) {
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
func NewUnbondingDelegationEntry(creationHeight int64, completionTime time.Time, balance math.Int) UnbondingDelegationEntry {
|
||||
func NewUnbondingDelegationEntry(creationHeight int64, completionTime time.Time, balance math.Int, unbondingID uint64) UnbondingDelegationEntry {
|
||||
return UnbondingDelegationEntry{
|
||||
CreationHeight: creationHeight,
|
||||
CompletionTime: completionTime,
|
||||
InitialBalance: balance,
|
||||
Balance: balance,
|
||||
CreationHeight: creationHeight,
|
||||
CompletionTime: completionTime,
|
||||
InitialBalance: balance,
|
||||
Balance: balance,
|
||||
UnbondingId: unbondingID,
|
||||
UnbondingOnHoldRefCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,24 +114,50 @@ func (e UnbondingDelegationEntry) IsMature(currentTime time.Time) bool {
|
||||
return !e.CompletionTime.After(currentTime)
|
||||
}
|
||||
|
||||
// OnHold - is the current entry on hold due to external modules
|
||||
func (e UnbondingDelegationEntry) OnHold() bool {
|
||||
return e.UnbondingOnHoldRefCount > 0
|
||||
}
|
||||
|
||||
// return the unbonding delegation entry
|
||||
func MustMarshalUBDE(cdc codec.BinaryCodec, ubd UnbondingDelegationEntry) []byte {
|
||||
return cdc.MustMarshal(&ubd)
|
||||
}
|
||||
|
||||
// unmarshal a unbonding delegation entry from a store value
|
||||
func MustUnmarshalUBDE(cdc codec.BinaryCodec, value []byte) UnbondingDelegationEntry {
|
||||
ubd, err := UnmarshalUBDE(cdc, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ubd
|
||||
}
|
||||
|
||||
// unmarshal a unbonding delegation entry from a store value
|
||||
func UnmarshalUBDE(cdc codec.BinaryCodec, value []byte) (ubd UnbondingDelegationEntry, err error) {
|
||||
err = cdc.Unmarshal(value, &ubd)
|
||||
return ubd, err
|
||||
}
|
||||
|
||||
// NewUnbondingDelegation - create a new unbonding delegation object
|
||||
//
|
||||
//nolint:interfacer
|
||||
func NewUnbondingDelegation(
|
||||
delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress,
|
||||
creationHeight int64, minTime time.Time, balance math.Int,
|
||||
creationHeight int64, minTime time.Time, balance math.Int, id uint64,
|
||||
) UnbondingDelegation {
|
||||
return UnbondingDelegation{
|
||||
DelegatorAddress: delegatorAddr.String(),
|
||||
ValidatorAddress: validatorAddr.String(),
|
||||
Entries: []UnbondingDelegationEntry{
|
||||
NewUnbondingDelegationEntry(creationHeight, minTime, balance),
|
||||
NewUnbondingDelegationEntry(creationHeight, minTime, balance, id),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AddEntry - append entry to the unbonding delegation
|
||||
func (ubd *UnbondingDelegation) AddEntry(creationHeight int64, minTime time.Time, balance math.Int) {
|
||||
func (ubd *UnbondingDelegation) AddEntry(creationHeight int64, minTime time.Time, balance math.Int, unbondingID uint64) {
|
||||
// Check the entries exists with creation_height and complete_time
|
||||
entryIndex := -1
|
||||
for index, ubdEntry := range ubd.Entries {
|
||||
@ -148,7 +176,7 @@ func (ubd *UnbondingDelegation) AddEntry(creationHeight int64, minTime time.Time
|
||||
ubd.Entries[entryIndex] = ubdEntry
|
||||
} else {
|
||||
// append the new unbond delegation entry
|
||||
entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance)
|
||||
entry := NewUnbondingDelegationEntry(creationHeight, minTime, balance, unbondingID)
|
||||
ubd.Entries = append(ubd.Entries, entry)
|
||||
}
|
||||
}
|
||||
@ -207,12 +235,14 @@ func (ubds UnbondingDelegations) String() (out string) {
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
func NewRedelegationEntry(creationHeight int64, completionTime time.Time, balance math.Int, sharesDst sdk.Dec) RedelegationEntry {
|
||||
func NewRedelegationEntry(creationHeight int64, completionTime time.Time, balance math.Int, sharesDst sdk.Dec, id uint64) RedelegationEntry {
|
||||
return RedelegationEntry{
|
||||
CreationHeight: creationHeight,
|
||||
CompletionTime: completionTime,
|
||||
InitialBalance: balance,
|
||||
SharesDst: sharesDst,
|
||||
CreationHeight: creationHeight,
|
||||
CompletionTime: completionTime,
|
||||
InitialBalance: balance,
|
||||
SharesDst: sharesDst,
|
||||
UnbondingId: id,
|
||||
UnbondingOnHoldRefCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,24 +257,29 @@ func (e RedelegationEntry) IsMature(currentTime time.Time) bool {
|
||||
return !e.CompletionTime.After(currentTime)
|
||||
}
|
||||
|
||||
// OnHold - is the current entry on hold due to external modules
|
||||
func (e RedelegationEntry) OnHold() bool {
|
||||
return e.UnbondingOnHoldRefCount > 0
|
||||
}
|
||||
|
||||
//nolint:interfacer
|
||||
func NewRedelegation(
|
||||
delegatorAddr sdk.AccAddress, validatorSrcAddr, validatorDstAddr sdk.ValAddress,
|
||||
creationHeight int64, minTime time.Time, balance math.Int, sharesDst sdk.Dec,
|
||||
creationHeight int64, minTime time.Time, balance math.Int, sharesDst sdk.Dec, id uint64,
|
||||
) Redelegation {
|
||||
return Redelegation{
|
||||
DelegatorAddress: delegatorAddr.String(),
|
||||
ValidatorSrcAddress: validatorSrcAddr.String(),
|
||||
ValidatorDstAddress: validatorDstAddr.String(),
|
||||
Entries: []RedelegationEntry{
|
||||
NewRedelegationEntry(creationHeight, minTime, balance, sharesDst),
|
||||
NewRedelegationEntry(creationHeight, minTime, balance, sharesDst, id),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AddEntry - append entry to the unbonding delegation
|
||||
func (red *Redelegation) AddEntry(creationHeight int64, minTime time.Time, balance math.Int, sharesDst sdk.Dec) {
|
||||
entry := NewRedelegationEntry(creationHeight, minTime, balance, sharesDst)
|
||||
func (red *Redelegation) AddEntry(creationHeight int64, minTime time.Time, balance math.Int, sharesDst sdk.Dec, id uint64) {
|
||||
entry := NewRedelegationEntry(creationHeight, minTime, balance, sharesDst, id)
|
||||
red.Entries = append(red.Entries, entry)
|
||||
}
|
||||
|
||||
@ -371,10 +406,10 @@ func NewRedelegationResponse(
|
||||
|
||||
// NewRedelegationEntryResponse creates a new RedelegationEntryResponse instance.
|
||||
func NewRedelegationEntryResponse(
|
||||
creationHeight int64, completionTime time.Time, sharesDst sdk.Dec, initialBalance, balance math.Int,
|
||||
creationHeight int64, completionTime time.Time, sharesDst sdk.Dec, initialBalance, balance math.Int, unbondingID uint64,
|
||||
) RedelegationEntryResponse {
|
||||
return RedelegationEntryResponse{
|
||||
RedelegationEntry: NewRedelegationEntry(creationHeight, completionTime, initialBalance, sharesDst),
|
||||
RedelegationEntry: NewRedelegationEntry(creationHeight, completionTime, initialBalance, sharesDst, unbondingID),
|
||||
Balance: balance,
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ func TestDelegationString(t *testing.T) {
|
||||
|
||||
func TestUnbondingDelegationEqual(t *testing.T) {
|
||||
ubd1 := types.NewUnbondingDelegation(sdk.AccAddress(valAddr1), valAddr2, 0,
|
||||
time.Unix(0, 0), sdk.NewInt(0))
|
||||
time.Unix(0, 0), sdk.NewInt(0), 1)
|
||||
ubd2 := ubd1
|
||||
|
||||
ok := ubd1.String() == ubd2.String()
|
||||
@ -49,7 +49,7 @@ func TestUnbondingDelegationEqual(t *testing.T) {
|
||||
|
||||
func TestUnbondingDelegationString(t *testing.T) {
|
||||
ubd := types.NewUnbondingDelegation(sdk.AccAddress(valAddr1), valAddr2, 0,
|
||||
time.Unix(0, 0), sdk.NewInt(0))
|
||||
time.Unix(0, 0), sdk.NewInt(0), 1)
|
||||
|
||||
require.NotEmpty(t, ubd.String())
|
||||
}
|
||||
@ -57,10 +57,10 @@ func TestUnbondingDelegationString(t *testing.T) {
|
||||
func TestRedelegationEqual(t *testing.T) {
|
||||
r1 := types.NewRedelegation(sdk.AccAddress(valAddr1), valAddr2, valAddr3, 0,
|
||||
time.Unix(0, 0), sdk.NewInt(0),
|
||||
math.LegacyNewDec(0))
|
||||
math.LegacyNewDec(0), 1)
|
||||
r2 := types.NewRedelegation(sdk.AccAddress(valAddr1), valAddr2, valAddr3, 0,
|
||||
time.Unix(0, 0), sdk.NewInt(0),
|
||||
math.LegacyNewDec(0))
|
||||
math.LegacyNewDec(0), 2)
|
||||
|
||||
ok := r1.String() == r2.String()
|
||||
require.True(t, ok)
|
||||
@ -75,7 +75,7 @@ func TestRedelegationEqual(t *testing.T) {
|
||||
func TestRedelegationString(t *testing.T) {
|
||||
r := types.NewRedelegation(sdk.AccAddress(valAddr1), valAddr2, valAddr3, 0,
|
||||
time.Unix(0, 0), sdk.NewInt(0),
|
||||
math.LegacyNewDec(10))
|
||||
math.LegacyNewDec(10), 1)
|
||||
|
||||
require.NotEmpty(t, r.String())
|
||||
}
|
||||
@ -112,8 +112,8 @@ func TestDelegationResponses(t *testing.T) {
|
||||
func TestRedelegationResponses(t *testing.T) {
|
||||
cdc := codec.NewLegacyAmino()
|
||||
entries := []types.RedelegationEntryResponse{
|
||||
types.NewRedelegationEntryResponse(0, time.Unix(0, 0), math.LegacyNewDec(5), sdk.NewInt(5), sdk.NewInt(5)),
|
||||
types.NewRedelegationEntryResponse(0, time.Unix(0, 0), math.LegacyNewDec(5), sdk.NewInt(5), sdk.NewInt(5)),
|
||||
types.NewRedelegationEntryResponse(0, time.Unix(0, 0), math.LegacyNewDec(5), sdk.NewInt(5), sdk.NewInt(5), 0),
|
||||
types.NewRedelegationEntryResponse(0, time.Unix(0, 0), math.LegacyNewDec(5), sdk.NewInt(5), sdk.NewInt(5), 0),
|
||||
}
|
||||
rdr1 := types.NewRedelegationResponse(sdk.AccAddress(valAddr1), valAddr2, valAddr3, entries)
|
||||
rdr2 := types.NewRedelegationResponse(sdk.AccAddress(valAddr2), valAddr1, valAddr3, entries)
|
||||
|
||||
@ -50,4 +50,6 @@ var (
|
||||
ErrNoHistoricalInfo = sdkerrors.Register(ModuleName, 38, "no historical info found")
|
||||
ErrEmptyValidatorPubKey = sdkerrors.Register(ModuleName, 39, "empty validator public key")
|
||||
ErrCommissionLTMinRate = sdkerrors.Register(ModuleName, 40, "commission cannot be less than min rate")
|
||||
ErrUnbondingNotFound = sdkerrors.Register(ModuleName, 41, "unbonding operation not found")
|
||||
ErrUnbondingOnHoldRefCountNegative = sdkerrors.Register(ModuleName, 42, "cannot un-hold unbonding operation that is not on hold")
|
||||
)
|
||||
|
||||
@ -61,7 +61,7 @@ type ValidatorSet interface {
|
||||
StakingTokenSupply(sdk.Context) math.Int // total staking token supply
|
||||
|
||||
// slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction
|
||||
Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) math.Int
|
||||
Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, Infraction) math.Int
|
||||
Jail(sdk.Context, sdk.ConsAddress) // jail a validator
|
||||
Unjail(sdk.Context, sdk.ConsAddress) // unjail a validator
|
||||
|
||||
@ -103,6 +103,7 @@ type StakingHooks interface {
|
||||
BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error // Must be called when a delegation is removed
|
||||
AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error
|
||||
BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) error
|
||||
AfterUnbondingInitiated(ctx sdk.Context, id uint64) error
|
||||
}
|
||||
|
||||
// StakingHooksWrapper is a wrapper for modules to inject StakingHooks using depinject.
|
||||
|
||||
@ -103,3 +103,12 @@ func (h MultiStakingHooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.V
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h MultiStakingHooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error {
|
||||
for i := range h {
|
||||
if err := h[i].AfterUnbondingInitiated(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -42,15 +42,44 @@ var (
|
||||
RedelegationByValSrcIndexKey = []byte{0x35} // prefix for each key for an redelegation, by source validator operator
|
||||
RedelegationByValDstIndexKey = []byte{0x36} // prefix for each key for an redelegation, by destination validator operator
|
||||
|
||||
UnbondingIDKey = []byte{0x37} // key for the counter for the incrementing id for UnbondingOperations
|
||||
UnbondingIndexKey = []byte{0x38} // prefix for an index for looking up unbonding operations by their IDs
|
||||
UnbondingTypeKey = []byte{0x39} // prefix for an index containing the type of unbonding operations
|
||||
|
||||
UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue
|
||||
RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue
|
||||
ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue
|
||||
|
||||
HistoricalInfoKey = []byte{0x50} // prefix for the historical info
|
||||
HistoricalInfoKey = []byte{0x50} // prefix for the historical info
|
||||
ValidatorUpdatesKey = []byte{0x61} // prefix for the end block validator updates key
|
||||
|
||||
ParamsKey = []byte{0x51} // prefix for parameters for module x/staking
|
||||
)
|
||||
|
||||
// UnbondingType defines the type of unbonding operation
|
||||
type UnbondingType int
|
||||
|
||||
const (
|
||||
UnbondingType_Undefined UnbondingType = iota
|
||||
UnbondingType_UnbondingDelegation
|
||||
UnbondingType_Redelegation
|
||||
UnbondingType_ValidatorUnbonding
|
||||
)
|
||||
|
||||
// Returns a key for an index containing the type of unbonding operations
|
||||
func GetUnbondingTypeKey(id uint64) []byte {
|
||||
bz := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bz, id)
|
||||
return append(UnbondingTypeKey, bz...)
|
||||
}
|
||||
|
||||
// Returns a key for the index for looking up UnbondingDelegations by the UnbondingDelegationEntries they contain
|
||||
func GetUnbondingIndexKey(id uint64) []byte {
|
||||
bz := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bz, id)
|
||||
return append(UnbondingIndexKey, bz...)
|
||||
}
|
||||
|
||||
// GetValidatorKey creates the key for the validator with address
|
||||
// VALUE: staking/Validator
|
||||
func GetValidatorKey(operatorAddr sdk.ValAddress) []byte {
|
||||
|
||||
@ -23,7 +23,7 @@ func TestParamsEqual(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func Test_validateParams(t *testing.T) {
|
||||
func TestValidateParams(t *testing.T) {
|
||||
params := types.DefaultParams()
|
||||
|
||||
// default params have no error
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -48,17 +48,18 @@ func NewValidator(operator sdk.ValAddress, pubKey cryptotypes.PubKey, descriptio
|
||||
}
|
||||
|
||||
return Validator{
|
||||
OperatorAddress: operator.String(),
|
||||
ConsensusPubkey: pkAny,
|
||||
Jailed: false,
|
||||
Status: Unbonded,
|
||||
Tokens: math.ZeroInt(),
|
||||
DelegatorShares: math.LegacyZeroDec(),
|
||||
Description: description,
|
||||
UnbondingHeight: int64(0),
|
||||
UnbondingTime: time.Unix(0, 0).UTC(),
|
||||
Commission: NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()),
|
||||
MinSelfDelegation: math.OneInt(),
|
||||
OperatorAddress: operator.String(),
|
||||
ConsensusPubkey: pkAny,
|
||||
Jailed: false,
|
||||
Status: Unbonded,
|
||||
Tokens: math.ZeroInt(),
|
||||
DelegatorShares: math.LegacyZeroDec(),
|
||||
Description: description,
|
||||
UnbondingHeight: int64(0),
|
||||
UnbondingTime: time.Unix(0, 0).UTC(),
|
||||
Commission: NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()),
|
||||
MinSelfDelegation: math.OneInt(),
|
||||
UnbondingOnHoldRefCount: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user