From 1d3ee78273b5a16372b04598148330253b901b1a Mon Sep 17 00:00:00 2001 From: Karolos Antoniadis Date: Mon, 25 Sep 2023 16:33:29 +0200 Subject: [PATCH] Use cached context to get tokens in undelegations and redelegations. --- testutil/keeper/mocks.go | 28 +++++++++ x/ccv/provider/keeper/punish_validator.go | 30 +++------ .../provider/keeper/punish_validator_test.go | 61 ++++++++++++++++++- x/ccv/types/expected_keepers.go | 2 + 4 files changed, 99 insertions(+), 22 deletions(-) diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 3df4110286..dffbc1ca09 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -307,6 +307,34 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockStakingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4, arg5) } +// SlashRedelegation mocks base method. +func (m *MockStakingKeeper) SlashRedelegation(arg0 types.Context, arg1 types4.Validator, arg2 types4.Redelegation, arg3 int64, arg4 types.Dec) types.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashRedelegation", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(types.Int) + return ret0 +} + +// SlashRedelegation indicates an expected call of SlashRedelegation. +func (mr *MockStakingKeeperMockRecorder) SlashRedelegation(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashRedelegation", reflect.TypeOf((*MockStakingKeeper)(nil).SlashRedelegation), arg0, arg1, arg2, arg3, arg4) +} + +// SlashUnbondingDelegation mocks base method. +func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types.Context, arg1 types4.UnbondingDelegation, arg2 int64, arg3 types.Dec) types.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashUnbondingDelegation", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(types.Int) + return ret0 +} + +// SlashUnbondingDelegation indicates an expected call of SlashUnbondingDelegation. +func (mr *MockStakingKeeperMockRecorder) SlashUnbondingDelegation(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashUnbondingDelegation", reflect.TypeOf((*MockStakingKeeper)(nil).SlashUnbondingDelegation), arg0, arg1, arg2, arg3) +} + // UnbondingCanComplete mocks base method. func (m *MockStakingKeeper) UnbondingCanComplete(ctx types.Context, id uint64) error { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/punish_validator.go b/x/ccv/provider/keeper/punish_validator.go index 755d39e055..3fc67afecb 100644 --- a/x/ccv/provider/keeper/punish_validator.go +++ b/x/ccv/provider/keeper/punish_validator.go @@ -1,8 +1,6 @@ package keeper import ( - "time" - sdk "github.com/cosmos/cosmos-sdk/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" @@ -44,33 +42,25 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr k.slashingKeeper.Tombstone(ctx, providerAddr.ToSdkConsAddr()) } -// ComputePowerToSlash computes the power to be slashed based on the tokens in non-matured (based on the -// provider `now` time) `undelegations` and `redelegations`, as well as the current `power` of the validator -func (k Keeper) ComputePowerToSlash(now time.Time, undelegations []stakingtypes.UnbondingDelegation, +// ComputePowerToSlash computes the power to be slashed based on the tokens in non-matured `undelegations` and +// `redelegations`, as well as the current `power` of the validator +func (k Keeper) ComputePowerToSlash(ctx sdk.Context, validator stakingtypes.Validator, undelegations []stakingtypes.UnbondingDelegation, redelegations []stakingtypes.Redelegation, power int64, powerReduction sdk.Int, ) int64 { // compute the total numbers of tokens currently being undelegated undelegationsInTokens := sdk.NewInt(0) + + cachedCtx, _ := ctx.CacheContext() for _, u := range undelegations { - for _, entry := range u.Entries { - if entry.IsMature(now) && !entry.OnHold() { - // undelegation no longer eligible for slashing, skip it - continue - } - undelegationsInTokens = undelegationsInTokens.Add(entry.InitialBalance) - } + amountSlashed := k.stakingKeeper.SlashUnbondingDelegation(cachedCtx, u, 0, sdk.NewDec(1)) + undelegationsInTokens = undelegationsInTokens.Add(amountSlashed) } // compute the total numbers of tokens currently being redelegated redelegationsInTokens := sdk.NewInt(0) for _, r := range redelegations { - for _, entry := range r.Entries { - if entry.IsMature(now) && !entry.OnHold() { - // redelegation no longer eligible for slashing, skip it - continue - } - redelegationsInTokens = redelegationsInTokens.Add(entry.InitialBalance) - } + amountSlashed := k.stakingKeeper.SlashRedelegation(cachedCtx, validator, r, 0, sdk.NewDec(1)) + redelegationsInTokens = redelegationsInTokens.Add(amountSlashed) } // The power we pass to staking's keeper `Slash` method is the current power of the validator together with the total @@ -105,7 +95,7 @@ func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsA redelegations := k.stakingKeeper.GetRedelegationsFromSrcValidator(ctx, validator.GetOperator()) lastPower := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) powerReduction := k.stakingKeeper.PowerReduction(ctx) - totalPower := k.ComputePowerToSlash(ctx.BlockHeader().Time, undelegations, redelegations, lastPower, powerReduction) + totalPower := k.ComputePowerToSlash(ctx, validator, undelegations, redelegations, lastPower, powerReduction) slashFraction := k.slashingKeeper.SlashFractionDoubleSign(ctx) k.stakingKeeper.Slash(ctx, providerAddr.ToSdkConsAddr(), 0, totalPower, slashFraction, stakingtypes.DoubleSign) diff --git a/x/ccv/provider/keeper/punish_validator_test.go b/x/ccv/provider/keeper/punish_validator_test.go index 2d7608b83b..1166fbf063 100644 --- a/x/ccv/provider/keeper/punish_validator_test.go +++ b/x/ccv/provider/keeper/punish_validator_test.go @@ -176,7 +176,7 @@ func createRedelegation(initialBalances []int64, completionTimes []time.Time) st // TestComputePowerToSlash tests that `ComputePowerToSlash` computes the correct power to be slashed based on // the tokens in non-mature undelegation and redelegation entries, as well as the current power of the validator func TestComputePowerToSlash(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() // undelegation or redelegation entries with completion time `now` have matured @@ -297,8 +297,39 @@ func TestComputePowerToSlash(t *testing.T) { }, } + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + validator, _ := stakingtypes.NewValidator(pubKey.Address().Bytes(), pubKey, stakingtypes.Description{}) + for _, tc := range testCases { - actualPower := providerKeeper.ComputePowerToSlash(now, + gomock.InOrder(mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), int64(0), sdk.NewDec(1)). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), int64(0), sdk.NewDec(1)). + DoAndReturn( + func(ctx sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + ) + + actualPower := providerKeeper.ComputePowerToSlash(ctx, validator, tc.undelegations, tc.redelegations, tc.power, tc.powerReduction) if tc.expectedPower != actualPower { @@ -386,6 +417,32 @@ func TestSlashValidator(t *testing.T) { mocks.MockStakingKeeper.EXPECT(). PowerReduction(ctx). Return(powerReduction), + mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) sdk.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), mocks.MockSlashingKeeper.EXPECT(). SlashFractionDoubleSign(ctx). Return(slashFraction), diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index e3505c6bb7..1aa2b6dc8f 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -30,6 +30,8 @@ type StakingKeeper interface { // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction Jail(sdk.Context, sdk.ConsAddress) // jail a validator Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, stakingtypes.InfractionType) + SlashUnbondingDelegation(sdk.Context, stakingtypes.UnbondingDelegation, int64, sdk.Dec) sdk.Int + SlashRedelegation(sdk.Context, stakingtypes.Validator, stakingtypes.Redelegation, int64, sdk.Dec) sdk.Int Unjail(ctx sdk.Context, addr sdk.ConsAddress) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) IterateLastValidatorPowers(ctx sdk.Context, cb func(addr sdk.ValAddress, power int64) (stop bool))