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:
Marko 2022-10-09 12:21:20 +02:00 committed by GitHub
parent 6efbedb1c7
commit 84675a6bf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 4337 additions and 1157 deletions

View File

@ -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

View File

@ -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];
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))
}

View 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))
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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())

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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])

View File

@ -597,6 +597,7 @@ func RedelegationsToRedelegationResponses(ctx sdk.Context, k *Keeper, redels typ
entry.SharesDst,
entry.InitialBalance,
val.TokensFromShares(entry.SharesDst).TruncateInt(),
entry.UnbondingId,
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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) })
}

View 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
}

View 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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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{

View File

@ -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())

View File

@ -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()

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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")
)

View File

@ -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.

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}