diff --git a/app/app.go b/app/app.go index ec4411e5..d5863260 100644 --- a/app/app.go +++ b/app/app.go @@ -528,6 +528,7 @@ func New( app.GetSubspace(valsetmoduletypes.ModuleName), app.StakingKeeper, minimumPigeonVersion, + sdk.DefaultPowerReduction, ) consensusRegistry := consensusmodulekeeper.NewRegistry() diff --git a/testutil/keeper/valset.go b/testutil/keeper/valset.go index 3a372907..cad5dfb7 100644 --- a/testutil/keeper/valset.go +++ b/testutil/keeper/valset.go @@ -44,6 +44,7 @@ func ValsetKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { paramsSubspace, nil, "v1.4.0", + sdk.DefaultPowerReduction, ) k.EvmKeeper = mocks.NewEvmKeeper(t) diff --git a/x/gravity/keeper/keeper.go b/x/gravity/keeper/keeper.go index e625ef60..63384453 100644 --- a/x/gravity/keeper/keeper.go +++ b/x/gravity/keeper/keeper.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" + sdkmath "cosmossdk.io/math" tmbytes "github.com/cometbft/cometbft/libs/bytes" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" @@ -33,7 +34,7 @@ type Keeper struct { bankKeeper types.BankKeeper SlashingKeeper types.SlashingKeeper DistributionKeeper types.DistributionKeeper - PowerReduction sdk.Int + PowerReduction sdkmath.Int hooks types.GravityHooks ReceiverModuleAccounts map[string]string SenderModuleAccounts map[string]string @@ -49,7 +50,7 @@ func NewKeeper( bankKeeper types.BankKeeper, slashingKeeper types.SlashingKeeper, distributionKeeper types.DistributionKeeper, - powerReduction sdk.Int, + powerReduction sdkmath.Int, receiverModuleAccounts map[string]string, senderModuleAccounts map[string]string, ) Keeper { diff --git a/x/paloma/keeper/keeper_integration_test.go b/x/paloma/keeper/keeper_integration_test.go index 90bf8ba9..f349f103 100644 --- a/x/paloma/keeper/keeper_integration_test.go +++ b/x/paloma/keeper/keeper_integration_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "fmt" "math/big" "testing" @@ -39,7 +40,9 @@ var _ = Describe("jailing validators with missing external chain infos", func() }) BeforeEach(func() { - vals = testutil.GenValidators(3, 100) + // Generate enough validators to ensure jailing is not prevented due to high + // amount of stake in network. + vals = testutil.GenValidators(10, 100) for _, val := range vals { a.StakingKeeper.SetValidator(ctx, val) @@ -52,6 +55,7 @@ var _ = Describe("jailing validators with missing external chain infos", func() // val[0] has everything // val[1] has only one chain // val[2] doesn't have anything + // All other vals have everything err := a.ValsetKeeper.AddExternalChainInfo(ctx, vals[0].GetOperator(), []*valsettypes.ExternalChainInfo{ { ChainType: "evm", @@ -76,6 +80,23 @@ var _ = Describe("jailing validators with missing external chain infos", func() }, }) Expect(err).To(BeNil()) + for i, v := range vals[3:] { + err := a.ValsetKeeper.AddExternalChainInfo(ctx, v.GetOperator(), []*valsettypes.ExternalChainInfo{ + { + ChainType: "evm", + ChainReferenceID: "c1", + Address: fmt.Sprintf("abc%d", i+3), + Pubkey: []byte(fmt.Sprintf("abc%d", i+3)), + }, + { + ChainType: "evm", + ChainReferenceID: "c2", + Address: fmt.Sprintf("abcd%d", i+3), + Pubkey: []byte(fmt.Sprintf("abcd%d", i+3)), + }, + }) + Expect(err).To(BeNil()) + } }) BeforeEach(func() { diff --git a/x/valset/keeper/keep_alive_test.go b/x/valset/keeper/keep_alive_test.go index affc4e40..0a67c97d 100644 --- a/x/valset/keeper/keep_alive_test.go +++ b/x/valset/keeper/keep_alive_test.go @@ -19,8 +19,10 @@ func TestJailingInactiveValidators(t *testing.T) { valBuild := func(id int, toBeJailed bool) (*mocks.StakingValidatorI, sdk.ValAddress) { val := sdk.ValAddress(fmt.Sprintf("validator_%d", id)) vali := mocks.NewStakingValidatorI(t) + stake := sdk.DefaultPowerReduction ms.StakingKeeper.On("Validator", mock.Anything, val).Return(vali) vali.On("IsJailed").Return(false) + vali.On("GetConsensusPower", k.powerReduction).Return(stake.Int64()) vali.On("IsBonded").Return(true) vali.On("GetOperator").Return(val) vali.On("GetStatus").Return(stakingtypes.Bonded) diff --git a/x/valset/keeper/keeper.go b/x/valset/keeper/keeper.go index 9e9b182b..43c4fb61 100644 --- a/x/valset/keeper/keeper.go +++ b/x/valset/keeper/keeper.go @@ -24,20 +24,21 @@ import ( const ( snapshotIDKey = "snapshot-id" maxNumOfAllowedExternalAccounts = 100 + cJailingNetworkShareProtection = 0.25 ) type Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - paramstore paramtypes.Subspace - staking types.StakingKeeper - ider keeperutil.IDGenerator - EvmKeeper types.EvmKeeper - + EvmKeeper types.EvmKeeper SnapshotListeners []types.OnSnapshotBuiltListener + cdc codec.BinaryCodec + ider keeperutil.IDGenerator + memKey storetypes.StoreKey minimumPigeonVersion string + paramstore paramtypes.Subspace + powerReduction sdkmath.Int + staking types.StakingKeeper + storeKey storetypes.StoreKey } func NewKeeper( @@ -47,6 +48,7 @@ func NewKeeper( ps paramtypes.Subspace, staking types.StakingKeeper, minimumPigeonVersion string, + powerReduction sdkmath.Int, ) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -60,6 +62,7 @@ func NewKeeper( paramstore: ps, staking: staking, minimumPigeonVersion: minimumPigeonVersion, + powerReduction: powerReduction, } k.ider = keeperutil.NewIDGenerator(keeperutil.StoreGetterFn(func(ctx sdk.Context) sdk.KVStore { return prefix.NewStore(ctx.KVStore(k.storeKey), []byte("IDs")) @@ -525,16 +528,26 @@ func (k Keeper) Jail(ctx sdk.Context, valAddr sdk.ValAddress, reason string) err if val.IsJailed() { return ErrValidatorAlreadyJailed.Format(valAddr.String()) } + + consensusPower := val.GetConsensusPower(k.powerReduction) + totalConsensusPower := int64(0) count := 0 k.staking.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) bool { if val.IsBonded() && !val.IsJailed() { + totalConsensusPower += val.GetConsensusPower(k.powerReduction) count++ } return false }) + if count == 1 { return ErrCannotJailValidator.Format(valAddr).WrapS("number of active validators would be zero then") } + + if float64(consensusPower)/float64(totalConsensusPower) > cJailingNetworkShareProtection { + return ErrCannotJailValidator.Format(valAddr).WrapS("validator stake holds over %v percent of entire network", cJailingNetworkShareProtection) + } + cons, err := val.GetConsAddr() if err != nil { return err diff --git a/x/valset/keeper/keeper_integration_test.go b/x/valset/keeper/keeper_integration_test.go index 7c579871..7fa85d7a 100644 --- a/x/valset/keeper/keeper_integration_test.go +++ b/x/valset/keeper/keeper_integration_test.go @@ -95,5 +95,19 @@ var _ = Describe("jaling validators", func() { Expect(err).To(MatchError(keeper.ErrValidatorAlreadyJailed)) }) }) + + Context("jailing validator with too much network stake", func() { + BeforeEach(func() { + By("change validator stake") + validators := testutil.GenValidators(1, 100) + a.StakingKeeper.SetValidator(ctx, validators[0]) + a.StakingKeeper.SetValidatorByConsAddr(ctx, validators[0]) + val = validators[0].GetOperator() + }) + It("returns an error", func() { + err := a.ValsetKeeper.Jail(ctx, val, "i am bored") + Expect(err).To(MatchError(keeper.ErrCannotJailValidator)) + }) + }) }) }) diff --git a/x/valset/keeper/setup_test.go b/x/valset/keeper/setup_test.go index a2abe1c1..4057cbe1 100644 --- a/x/valset/keeper/setup_test.go +++ b/x/valset/keeper/setup_test.go @@ -56,6 +56,7 @@ func newValsetKeeper(t testing.TB) (*Keeper, mockedServices, sdk.Context) { paramsSubspace, ms.StakingKeeper, "v1.4.0", + sdk.DefaultPowerReduction, ) k.EvmKeeper = ms.EvmKeeper