diff --git a/CHANGELOG.md b/CHANGELOG.md index 04115edec815..2bdf084b0d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* (x/slashing) [#16246](https://github.com/cosmos/cosmos-sdk/issues/16246) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, and methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. `GetValidatorSigningInfo` now returns an error instead of a `found bool`, the error can be `nil` (found), `ErrNoSigningInfoFound` (not found) and any other error. * (x/mint) [#16179](https://github.com/cosmos/cosmos-sdk/issues/16179) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, and methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. * (x/crisis) [#16216](https://github.com/cosmos/cosmos-sdk/issues/16216) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error` instead of panicking. * (x/gov) [#15988](https://github.com/cosmos/cosmos-sdk/issues/15988) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error` (instead of panicking or returning a `found bool`). Iterators callback functions now return an error instead of a `bool`. diff --git a/UPGRADING.md b/UPGRADING.md index d55480a3e2ad..76d422abc892 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -84,8 +84,9 @@ The following modules `NewKeeper` function now take a `KVStoreService` instead o * `x/gov` * `x/mint` * `x/nft` +* `x/slashing` -User manually wiring their chain need to use the `runtime.NewKVStoreService` method to create a `KVStoreService` from a `StoreKey`: +Users manually wiring their chain need to use the `runtime.NewKVStoreService` method to create a `KVStoreService` from a `StoreKey`: ```diff app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper( @@ -105,6 +106,7 @@ The following modules' `Keeper` methods now take in a `context.Context` instead * `x/distribution` * `x/evidence` * `x/gov` +* `x/slashing` **Users using depinject do not need any changes, this is automatically done for them.** diff --git a/simapp/app.go b/simapp/app.go index 904a0e0818bd..57c6baf2e8e9 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -324,7 +324,7 @@ func NewSimApp( app.DistrKeeper = distrkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) app.SlashingKeeper = slashingkeeper.NewKeeper( - appCodec, legacyAmino, keys[slashingtypes.StoreKey], app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, legacyAmino, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) invCheckPeriod := cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) diff --git a/tests/integration/evidence/keeper/infraction_test.go b/tests/integration/evidence/keeper/infraction_test.go index cf7686a20d1a..1ea9c25c9d43 100644 --- a/tests/integration/evidence/keeper/infraction_test.go +++ b/tests/integration/evidence/keeper/infraction_test.go @@ -119,7 +119,7 @@ func initFixture(t testing.TB) *fixture { stakingKeeper := stakingkeeper.NewKeeper(cdc, keys[stakingtypes.StoreKey], accountKeeper, bankKeeper, authority.String()) - slashingKeeper := slashingkeeper.NewKeeper(cdc, codec.NewLegacyAmino(), keys[slashingtypes.StoreKey], stakingKeeper, authority.String()) + slashingKeeper := slashingkeeper.NewKeeper(cdc, codec.NewLegacyAmino(), runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), stakingKeeper, authority.String()) evidenceKeeper := keeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), stakingKeeper, slashingKeeper, address.NewBech32Codec("cosmos"), runtime.ProvideCometInfoService()) router := evidencetypes.NewRouter() diff --git a/tests/integration/slashing/keeper/keeper_test.go b/tests/integration/slashing/keeper/keeper_test.go index a9073f2b1c20..c7eb5f36631a 100644 --- a/tests/integration/slashing/keeper/keeper_test.go +++ b/tests/integration/slashing/keeper/keeper_test.go @@ -90,7 +90,7 @@ func initFixture(t testing.TB) *fixture { stakingKeeper := stakingkeeper.NewKeeper(cdc, keys[stakingtypes.StoreKey], accountKeeper, bankKeeper, authority.String()) - slashingKeeper := slashingkeeper.NewKeeper(cdc, &codec.LegacyAmino{}, keys[slashingtypes.StoreKey], stakingKeeper, authority.String()) + slashingKeeper := slashingkeeper.NewKeeper(cdc, &codec.LegacyAmino{}, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), stakingKeeper, authority.String()) bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper, nil) stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper, nil) @@ -214,17 +214,21 @@ func TestHandleNewValidator(t *testing.T) { pks := simtestutil.CreateTestPubKeys(1) addr, val := f.valAddrs[0], pks[0] tstaking := stakingtestutil.NewHelper(t, f.ctx, f.stakingKeeper) - f.ctx = f.ctx.WithBlockHeight(f.slashingKeeper.SignedBlocksWindow(f.ctx) + 1) + signedBlocksWindow, err := f.slashingKeeper.SignedBlocksWindow(f.ctx) + assert.NilError(t, err) + f.ctx = f.ctx.WithBlockHeight(signedBlocksWindow + 1) - f.slashingKeeper.AddPubkey(f.ctx, pks[0]) + assert.NilError(t, f.slashingKeeper.AddPubkey(f.ctx, pks[0])) info := slashingtypes.NewValidatorSigningInfo(sdk.ConsAddress(val.Address()), f.ctx.BlockHeight(), int64(0), time.Unix(0, 0), false, int64(0)) - f.slashingKeeper.SetValidatorSigningInfo(f.ctx, sdk.ConsAddress(val.Address()), info) + err = f.slashingKeeper.SetValidatorSigningInfo(f.ctx, sdk.ConsAddress(val.Address()), info) + assert.NilError(t, err) // Validator created amt := tstaking.CreateValidatorWithValPower(addr, val, 100, true) - f.stakingKeeper.EndBlocker(f.ctx) + _, err = f.stakingKeeper.EndBlocker(f.ctx) + assert.NilError(t, err) assert.DeepEqual( t, f.bankKeeper.GetAllBalances(f.ctx, sdk.AccAddress(addr)), sdk.NewCoins(sdk.NewCoin(f.stakingKeeper.GetParams(f.ctx).BondDenom, testutil.InitTokens.Sub(amt))), @@ -232,13 +236,13 @@ func TestHandleNewValidator(t *testing.T) { assert.DeepEqual(t, amt, f.stakingKeeper.Validator(f.ctx, addr).GetBondedTokens()) // Now a validator, for two blocks - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), 100, comet.BlockIDFlagCommit) - f.ctx = f.ctx.WithBlockHeight(f.slashingKeeper.SignedBlocksWindow(f.ctx) + 2) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), 100, comet.BlockIDFlagAbsent) + assert.NilError(t, f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), 100, comet.BlockIDFlagCommit)) + f.ctx = f.ctx.WithBlockHeight(signedBlocksWindow + 2) + assert.NilError(t, f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), 100, comet.BlockIDFlagAbsent)) info, found := f.slashingKeeper.GetValidatorSigningInfo(f.ctx, sdk.ConsAddress(val.Address())) assert.Assert(t, found) - assert.Equal(t, f.slashingKeeper.SignedBlocksWindow(f.ctx)+1, info.StartHeight) + assert.Equal(t, signedBlocksWindow+1, info.StartHeight) assert.Equal(t, int64(2), info.IndexOffset) assert.Equal(t, int64(1), info.MissedBlocksCounter) assert.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) @@ -262,30 +266,41 @@ func TestHandleAlreadyJailed(t *testing.T) { power := int64(100) tstaking := stakingtestutil.NewHelper(t, f.ctx, f.stakingKeeper) - f.slashingKeeper.AddPubkey(f.ctx, pks[0]) + err := f.slashingKeeper.AddPubkey(f.ctx, pks[0]) + assert.NilError(t, err) info := slashingtypes.NewValidatorSigningInfo(sdk.ConsAddress(val.Address()), f.ctx.BlockHeight(), int64(0), time.Unix(0, 0), false, int64(0)) - f.slashingKeeper.SetValidatorSigningInfo(f.ctx, sdk.ConsAddress(val.Address()), info) + assert.NilError(t, f.slashingKeeper.SetValidatorSigningInfo(f.ctx, sdk.ConsAddress(val.Address()), info)) amt := tstaking.CreateValidatorWithValPower(addr, val, power, true) - f.stakingKeeper.EndBlocker(f.ctx) + _, err = f.stakingKeeper.EndBlocker(f.ctx) + assert.NilError(t, err) + + signedBlocksWindow, err := f.slashingKeeper.SignedBlocksWindow(f.ctx) + assert.NilError(t, err) // 1000 first blocks OK height := int64(0) - for ; height < f.slashingKeeper.SignedBlocksWindow(f.ctx); height++ { + for ; height < signedBlocksWindow; height++ { f.ctx = f.ctx.WithBlockHeight(height) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagCommit) + err = f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagCommit) + assert.NilError(t, err) } + minSignedPerWindow, err := f.slashingKeeper.MinSignedPerWindow(f.ctx) + assert.NilError(t, err) + // 501 blocks missed - for ; height < f.slashingKeeper.SignedBlocksWindow(f.ctx)+(f.slashingKeeper.SignedBlocksWindow(f.ctx)-f.slashingKeeper.MinSignedPerWindow(f.ctx))+1; height++ { + for ; height < signedBlocksWindow+(signedBlocksWindow-minSignedPerWindow)+1; height++ { f.ctx = f.ctx.WithBlockHeight(height) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagAbsent) + err = f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagAbsent) + assert.NilError(t, err) } // end block - f.stakingKeeper.EndBlocker(f.ctx) + _, err = f.stakingKeeper.EndBlocker(f.ctx) + assert.NilError(t, err) // validator should have been jailed and slashed validator, _ := f.stakingKeeper.GetValidatorByConsAddr(f.ctx, sdk.GetConsAddress(val)) @@ -297,7 +312,7 @@ func TestHandleAlreadyJailed(t *testing.T) { // another block missed f.ctx = f.ctx.WithBlockHeight(height) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagAbsent) + assert.NilError(t, f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagAbsent)) // validator should not have been slashed twice validator, _ = f.stakingKeeper.GetValidatorByConsAddr(f.ctx, sdk.GetConsAddress(val)) @@ -324,10 +339,10 @@ func TestValidatorDippingInAndOut(t *testing.T) { tstaking := stakingtestutil.NewHelper(t, f.ctx, f.stakingKeeper) valAddr := sdk.ValAddress(addr) - f.slashingKeeper.AddPubkey(f.ctx, pks[0]) + assert.NilError(t, f.slashingKeeper.AddPubkey(f.ctx, pks[0])) info := slashingtypes.NewValidatorSigningInfo(consAddr, f.ctx.BlockHeight(), int64(0), time.Unix(0, 0), false, int64(0)) - f.slashingKeeper.SetValidatorSigningInfo(f.ctx, consAddr, info) + assert.NilError(t, f.slashingKeeper.SetValidatorSigningInfo(f.ctx, consAddr, info)) tstaking.CreateValidatorWithValPower(valAddr, val, power, true) validatorUpdates, err := f.stakingKeeper.EndBlocker(f.ctx) @@ -339,7 +354,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { height := int64(0) for ; height < int64(100); height++ { f.ctx = f.ctx.WithBlockHeight(height) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagCommit) + assert.NilError(t, f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), power, comet.BlockIDFlagCommit)) } // kick first validator out of validator set @@ -364,26 +379,32 @@ func TestValidatorDippingInAndOut(t *testing.T) { newPower := power + 50 // validator misses a block - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), newPower, comet.BlockIDFlagAbsent) + assert.NilError(t, f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), newPower, comet.BlockIDFlagAbsent)) height++ // shouldn't be jailed/kicked yet tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) + signedBlocksWindow, err := f.slashingKeeper.SignedBlocksWindow(f.ctx) + assert.NilError(t, err) + // validator misses an additional 500 more blocks within the SignedBlockWindow (here 1000 blocks). - latest := f.slashingKeeper.SignedBlocksWindow(f.ctx) + height + latest := signedBlocksWindow + height // misses 500 blocks + within the signing windows i.e. 700-1700 // validators misses all 1000 blocks of a SignedBlockWindows for ; height < latest+1; height++ { - f.slashingKeeper.HandleValidatorSignature(f.ctx.WithBlockHeight(height), val.Address(), newPower, comet.BlockIDFlagAbsent) + err = f.slashingKeeper.HandleValidatorSignature(f.ctx.WithBlockHeight(height), val.Address(), newPower, comet.BlockIDFlagAbsent) + assert.NilError(t, err) } // should now be jailed & kicked - f.stakingKeeper.EndBlocker(f.ctx) + _, err = f.stakingKeeper.EndBlocker(f.ctx) + assert.NilError(t, err) tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true) info = slashingtypes.NewValidatorSigningInfo(consAddr, f.ctx.BlockHeight(), int64(0), time.Unix(0, 0), false, int64(0)) - f.slashingKeeper.SetValidatorSigningInfo(f.ctx, consAddr, info) + err = f.slashingKeeper.SetValidatorSigningInfo(f.ctx, consAddr, info) + assert.NilError(t, err) // check all the signing information signInfo, found := f.slashingKeeper.GetValidatorSigningInfo(f.ctx, consAddr) @@ -397,15 +418,18 @@ func TestValidatorDippingInAndOut(t *testing.T) { f.ctx = f.ctx.WithBlockHeight(height) info = slashingtypes.NewValidatorSigningInfo(consAddr, f.ctx.BlockHeight(), int64(0), time.Unix(0, 0), false, int64(0)) - f.slashingKeeper.SetValidatorSigningInfo(f.ctx, consAddr, info) + err = f.slashingKeeper.SetValidatorSigningInfo(f.ctx, consAddr, info) + assert.NilError(t, err) // validator rejoins and starts signing again f.stakingKeeper.Unjail(f.ctx, consAddr) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), newPower, comet.BlockIDFlagCommit) + err = f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), newPower, comet.BlockIDFlagCommit) + assert.NilError(t, err) // validator should not be kicked since we reset counter/array when it was jailed - f.stakingKeeper.EndBlocker(f.ctx) + _, err = f.stakingKeeper.EndBlocker(f.ctx) + assert.NilError(t, err) tstaking.CheckValidator(valAddr, stakingtypes.Bonded, false) // check start height is correctly set @@ -413,14 +437,20 @@ func TestValidatorDippingInAndOut(t *testing.T) { assert.Assert(t, found) assert.Equal(t, height, signInfo.StartHeight) + minSignedPerWindow, err := f.slashingKeeper.MinSignedPerWindow(f.ctx) + assert.NilError(t, err) + // validator misses 501 blocks after SignedBlockWindow period (1000 blocks) - latest = f.slashingKeeper.SignedBlocksWindow(f.ctx) + height - for ; height < latest+f.slashingKeeper.MinSignedPerWindow(f.ctx); height++ { + latest = signedBlocksWindow + height + for ; height < latest+minSignedPerWindow; height++ { f.ctx = f.ctx.WithBlockHeight(height) - f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), newPower, comet.BlockIDFlagAbsent) + err = f.slashingKeeper.HandleValidatorSignature(f.ctx, val.Address(), newPower, comet.BlockIDFlagAbsent) + assert.NilError(t, err) } // validator should now be jailed & kicked - f.stakingKeeper.EndBlocker(f.ctx) + _, err = f.stakingKeeper.EndBlocker(f.ctx) + assert.NilError(t, err) + tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true) } diff --git a/x/evidence/keeper/infraction.go b/x/evidence/keeper/infraction.go index 60ff6a40e18d..c91b033f5b67 100644 --- a/x/evidence/keeper/infraction.go +++ b/x/evidence/keeper/infraction.go @@ -37,7 +37,7 @@ func (k Keeper) handleEquivocationEvidence(ctx context.Context, evidence *types. } if !validator.GetOperator().Empty() { - if _, err := k.slashingKeeper.GetPubkey(sdkCtx, consAddr.Bytes()); err != nil { + if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil { // Ignore evidence that cannot be handled. // // NOTE: We used to panic with: @@ -76,12 +76,12 @@ func (k Keeper) handleEquivocationEvidence(ctx context.Context, evidence *types. } } - if ok := k.slashingKeeper.HasValidatorSigningInfo(sdkCtx, consAddr); !ok { + 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(sdkCtx, consAddr) { + if k.slashingKeeper.IsTombstoned(ctx, consAddr) { logger.Info( "ignored equivocation; validator already tombstoned", "validator", consAddr, @@ -110,21 +110,39 @@ func (k Keeper) handleEquivocationEvidence(ctx context.Context, evidence *types. // to/by CometBFT. This value is validator.Tokens as sent to CometBFT via // ABCI, and now received as evidence. The fraction is passed in to separately // to slash unbonding and rebonding delegations. - k.slashingKeeper.SlashWithInfractionReason( - sdkCtx, + slashFractionDoubleSign, err := k.slashingKeeper.SlashFractionDoubleSign(ctx) + if err != nil { + return err + } + + err = k.slashingKeeper.SlashWithInfractionReason( + ctx, consAddr, - k.slashingKeeper.SlashFractionDoubleSign(sdkCtx), + slashFractionDoubleSign, evidence.GetValidatorPower(), distributionHeight, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN, ) + if err != nil { + return err + } // Jail the validator if not already jailed. This will begin unbonding the // validator if not already unbonding (tombstoned). if !validator.IsJailed() { - k.slashingKeeper.Jail(sdkCtx, consAddr) + err = k.slashingKeeper.Jail(ctx, consAddr) + if err != nil { + return err + } } - k.slashingKeeper.JailUntil(sdkCtx, consAddr, types.DoubleSignJailEndTime) - k.slashingKeeper.Tombstone(sdkCtx, consAddr) + err = k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime) + if err != nil { + return err + } + + err = k.slashingKeeper.Tombstone(ctx, consAddr) + if err != nil { + return err + } return k.SetEvidence(ctx, evidence) } diff --git a/x/evidence/testutil/expected_keepers_mocks.go b/x/evidence/testutil/expected_keepers_mocks.go index 98b450424047..cbfc0dd44a71 100644 --- a/x/evidence/testutil/expected_keepers_mocks.go +++ b/x/evidence/testutil/expected_keepers_mocks.go @@ -92,7 +92,7 @@ func (m *MockSlashingKeeper) EXPECT() *MockSlashingKeeperMockRecorder { } // GetPubkey mocks base method. -func (m *MockSlashingKeeper) GetPubkey(arg0 types0.Context, arg1 types.Address) (types.PubKey, error) { +func (m *MockSlashingKeeper) GetPubkey(arg0 context.Context, arg1 types.Address) (types.PubKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPubkey", arg0, arg1) ret0, _ := ret[0].(types.PubKey) @@ -107,7 +107,7 @@ func (mr *MockSlashingKeeperMockRecorder) GetPubkey(arg0, arg1 interface{}) *gom } // HasValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) HasValidatorSigningInfo(arg0 types0.Context, arg1 types0.ConsAddress) bool { +func (m *MockSlashingKeeper) HasValidatorSigningInfo(arg0 context.Context, arg1 types0.ConsAddress) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasValidatorSigningInfo", arg0, arg1) ret0, _ := ret[0].(bool) @@ -121,7 +121,7 @@ func (mr *MockSlashingKeeperMockRecorder) HasValidatorSigningInfo(arg0, arg1 int } // IsTombstoned mocks base method. -func (m *MockSlashingKeeper) IsTombstoned(arg0 types0.Context, arg1 types0.ConsAddress) bool { +func (m *MockSlashingKeeper) IsTombstoned(arg0 context.Context, arg1 types0.ConsAddress) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsTombstoned", arg0, arg1) ret0, _ := ret[0].(bool) @@ -135,9 +135,11 @@ func (mr *MockSlashingKeeperMockRecorder) IsTombstoned(arg0, arg1 interface{}) * } // Jail mocks base method. -func (m *MockSlashingKeeper) Jail(arg0 types0.Context, arg1 types0.ConsAddress) { +func (m *MockSlashingKeeper) Jail(arg0 context.Context, arg1 types0.ConsAddress) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "Jail", arg0, arg1) + ret := m.ctrl.Call(m, "Jail", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 } // Jail indicates an expected call of Jail. @@ -147,9 +149,11 @@ func (mr *MockSlashingKeeperMockRecorder) Jail(arg0, arg1 interface{}) *gomock.C } // JailUntil mocks base method. -func (m *MockSlashingKeeper) JailUntil(arg0 types0.Context, arg1 types0.ConsAddress, arg2 time.Time) { +func (m *MockSlashingKeeper) JailUntil(arg0 context.Context, arg1 types0.ConsAddress, arg2 time.Time) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "JailUntil", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "JailUntil", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 } // JailUntil indicates an expected call of JailUntil. @@ -159,9 +163,11 @@ func (mr *MockSlashingKeeperMockRecorder) JailUntil(arg0, arg1, arg2 interface{} } // Slash mocks base method. -func (m *MockSlashingKeeper) Slash(arg0 types0.Context, arg1 types0.ConsAddress, arg2 math.LegacyDec, arg3, arg4 int64) { +func (m *MockSlashingKeeper) Slash(arg0 context.Context, arg1 types0.ConsAddress, arg2 math.LegacyDec, arg3, arg4 int64) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "Slash", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 } // Slash indicates an expected call of Slash. @@ -171,11 +177,12 @@ func (mr *MockSlashingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 int } // SlashFractionDoubleSign mocks base method. -func (m *MockSlashingKeeper) SlashFractionDoubleSign(arg0 types0.Context) math.LegacyDec { +func (m *MockSlashingKeeper) SlashFractionDoubleSign(arg0 context.Context) (math.LegacyDec, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashFractionDoubleSign", arg0) ret0, _ := ret[0].(math.LegacyDec) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // SlashFractionDoubleSign indicates an expected call of SlashFractionDoubleSign. @@ -185,9 +192,11 @@ func (mr *MockSlashingKeeperMockRecorder) SlashFractionDoubleSign(arg0 interface } // SlashWithInfractionReason mocks base method. -func (m *MockSlashingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2 math.LegacyDec, arg3, arg4 int64, arg5 types1.Infraction) { +func (m *MockSlashingKeeper) SlashWithInfractionReason(arg0 context.Context, arg1 types0.ConsAddress, arg2 math.LegacyDec, arg3, arg4 int64, arg5 types1.Infraction) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "SlashWithInfractionReason", arg0, arg1, arg2, arg3, arg4, arg5) + ret := m.ctrl.Call(m, "SlashWithInfractionReason", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(error) + return ret0 } // SlashWithInfractionReason indicates an expected call of SlashWithInfractionReason. @@ -197,9 +206,11 @@ func (mr *MockSlashingKeeperMockRecorder) SlashWithInfractionReason(arg0, arg1, } // Tombstone mocks base method. -func (m *MockSlashingKeeper) Tombstone(arg0 types0.Context, arg1 types0.ConsAddress) { +func (m *MockSlashingKeeper) Tombstone(arg0 context.Context, arg1 types0.ConsAddress) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "Tombstone", arg0, arg1) + ret := m.ctrl.Call(m, "Tombstone", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 } // Tombstone indicates an expected call of Tombstone. diff --git a/x/evidence/types/expected_keepers.go b/x/evidence/types/expected_keepers.go index 3405c5ed8bd5..cca18222cc9b 100644 --- a/x/evidence/types/expected_keepers.go +++ b/x/evidence/types/expected_keepers.go @@ -23,15 +23,15 @@ type ( // SlashingKeeper defines the slashing module interface contract needed by the // evidence module. SlashingKeeper interface { - GetPubkey(sdk.Context, cryptotypes.Address) (cryptotypes.PubKey, error) - IsTombstoned(sdk.Context, sdk.ConsAddress) bool - HasValidatorSigningInfo(sdk.Context, sdk.ConsAddress) bool - Tombstone(sdk.Context, sdk.ConsAddress) - Slash(sdk.Context, sdk.ConsAddress, sdkmath.LegacyDec, int64, int64) - SlashWithInfractionReason(sdk.Context, sdk.ConsAddress, sdkmath.LegacyDec, int64, int64, stakingtypes.Infraction) - SlashFractionDoubleSign(sdk.Context) sdkmath.LegacyDec - Jail(sdk.Context, sdk.ConsAddress) - JailUntil(sdk.Context, sdk.ConsAddress, time.Time) + GetPubkey(context.Context, cryptotypes.Address) (cryptotypes.PubKey, error) + IsTombstoned(context.Context, sdk.ConsAddress) bool + HasValidatorSigningInfo(context.Context, sdk.ConsAddress) bool + Tombstone(context.Context, sdk.ConsAddress) error + Slash(context.Context, sdk.ConsAddress, sdkmath.LegacyDec, int64, int64) error + SlashWithInfractionReason(context.Context, sdk.ConsAddress, sdkmath.LegacyDec, int64, int64, stakingtypes.Infraction) error + SlashFractionDoubleSign(context.Context) (sdkmath.LegacyDec, error) + Jail(context.Context, sdk.ConsAddress) error + JailUntil(context.Context, sdk.ConsAddress, time.Time) error } // AccountKeeper define the account keeper interface contracted needed by the evidence module diff --git a/x/slashing/abci.go b/x/slashing/abci.go index 8e17edb06f2f..af987c394a3b 100644 --- a/x/slashing/abci.go +++ b/x/slashing/abci.go @@ -1,9 +1,11 @@ package slashing import ( + "context" "time" "cosmossdk.io/core/comet" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing/keeper" @@ -12,13 +14,18 @@ import ( // BeginBlocker check for infraction evidence or downtime of validators // on every begin block -func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) // Iterate over all the validators which *should* have signed this block // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) - for _, voteInfo := range ctx.VoteInfos() { - k.HandleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, comet.BlockIDFlag(voteInfo.BlockIdFlag)) + sdkCtx := sdk.UnwrapSDKContext(ctx) + for _, voteInfo := range sdkCtx.VoteInfos() { + err := k.HandleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, comet.BlockIDFlag(voteInfo.BlockIdFlag)) + if err != nil { + return err + } } + return nil } diff --git a/x/slashing/abci_test.go b/x/slashing/abci_test.go index 7b08eb598bc3..0f03b1aafe0b 100644 --- a/x/slashing/abci_test.go +++ b/x/slashing/abci_test.go @@ -67,10 +67,11 @@ func TestBeginBlocker(t *testing.T) { BlockIdFlag: cmtproto.BlockIDFlagCommit, }}) - slashing.BeginBlocker(ctx, slashingKeeper) + err = slashing.BeginBlocker(ctx, slashingKeeper) + require.NoError(t, err) - info, found := slashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) - require.True(t, found) + info, err := slashingKeeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) + require.NoError(t, err) require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, int64(1), info.IndexOffset) require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) @@ -78,30 +79,37 @@ func TestBeginBlocker(t *testing.T) { height := int64(0) + signedBlocksWindow, err := slashingKeeper.SignedBlocksWindow(ctx) + require.NoError(t, err) // for 100 blocks, mark the validator as having signed - for ; height < slashingKeeper.SignedBlocksWindow(ctx); height++ { + for ; height < signedBlocksWindow; height++ { ctx = ctx.WithBlockHeight(height). WithVoteInfos([]abci.VoteInfo{{ Validator: val, BlockIdFlag: cmtproto.BlockIDFlagCommit, }}) - slashing.BeginBlocker(ctx, slashingKeeper) + err = slashing.BeginBlocker(ctx, slashingKeeper) + require.NoError(t, err) } + minSignedPerWindow, err := slashingKeeper.MinSignedPerWindow(ctx) + require.NoError(t, err) // for 50 blocks, mark the validator as having not signed - for ; height < ((slashingKeeper.SignedBlocksWindow(ctx) * 2) - slashingKeeper.MinSignedPerWindow(ctx) + 1); height++ { + for ; height < ((signedBlocksWindow * 2) - minSignedPerWindow + 1); height++ { ctx = ctx.WithBlockHeight(height). WithVoteInfos([]abci.VoteInfo{{ Validator: val, BlockIdFlag: cmtproto.BlockIDFlagAbsent, }}) - slashing.BeginBlocker(ctx, slashingKeeper) + err = slashing.BeginBlocker(ctx, slashingKeeper) + require.NoError(t, err) } // end block - stakingKeeper.EndBlocker(ctx) + _, err = stakingKeeper.EndBlocker(ctx) + require.NoError(t, err) // validator should be jailed validator, found := stakingKeeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 42f72963ad10..612df7b09582 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -102,8 +102,8 @@ func TestSlashingMsgs(t *testing.T) { unjailMsg := &types.MsgUnjail{ValidatorAddr: sdk.ValAddress(addr1).String()} ctxCheck = app.BaseApp.NewContext(true, cmtproto.Header{}) - _, found = slashingKeeper.GetValidatorSigningInfo(ctxCheck, sdk.ConsAddress(valAddr)) - require.True(t, found) + _, err = slashingKeeper.GetValidatorSigningInfo(ctxCheck, sdk.ConsAddress(valAddr)) + require.NoError(t, err) // unjail should fail with unknown validator header = cmtproto.Header{Height: app.LastBlockHeight() + 1} diff --git a/x/slashing/keeper/genesis.go b/x/slashing/keeper/genesis.go index dda2da2431d6..5fc8586d3946 100644 --- a/x/slashing/keeper/genesis.go +++ b/x/slashing/keeper/genesis.go @@ -51,7 +51,10 @@ func (keeper Keeper) InitGenesis(ctx sdk.Context, stakingKeeper types.StakingKee // to a genesis file, which can be imported again // with InitGenesis func (keeper Keeper) ExportGenesis(ctx sdk.Context) (data *types.GenesisState) { - params := keeper.GetParams(ctx) + params, err := keeper.GetParams(ctx) + if err != nil { + panic(err) + } signingInfos := make([]types.SigningInfo, 0) missedBlocks := make([]types.ValidatorMissedBlocks, 0) keeper.IterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) { @@ -61,7 +64,10 @@ func (keeper Keeper) ExportGenesis(ctx sdk.Context) (data *types.GenesisState) { ValidatorSigningInfo: info, }) - localMissedBlocks := keeper.GetValidatorMissedBlocks(ctx, address) + localMissedBlocks, err := keeper.GetValidatorMissedBlocks(ctx, address) + if err != nil { + panic(err) + } missedBlocks = append(missedBlocks, types.ValidatorMissedBlocks{ Address: bechAddr, diff --git a/x/slashing/keeper/genesis_test.go b/x/slashing/keeper/genesis_test.go index 3db4d79b580d..eac73841f166 100644 --- a/x/slashing/keeper/genesis_test.go +++ b/x/slashing/keeper/genesis_test.go @@ -33,8 +33,10 @@ func (s *KeeperTestSuite) TestExportAndInitGenesis() { require.Equal(genesisState.SigningInfos[0].ValidatorSigningInfo, info1) // Tombstone validators after genesis shouldn't effect genesis state - keeper.Tombstone(ctx, consAddr1) - keeper.Tombstone(ctx, consAddr2) + err := keeper.Tombstone(ctx, consAddr1) + require.NoError(err) + err = keeper.Tombstone(ctx, consAddr2) + require.NoError(err) ok := keeper.IsTombstoned(ctx, consAddr1) require.True(ok) diff --git a/x/slashing/keeper/grpc_query.go b/x/slashing/keeper/grpc_query.go index 74b59bb5259d..f702edc85ba0 100644 --- a/x/slashing/keeper/grpc_query.go +++ b/x/slashing/keeper/grpc_query.go @@ -8,6 +8,7 @@ import ( "cosmossdk.io/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/x/slashing/types" @@ -24,19 +25,18 @@ func NewQuerier(keeper Keeper) Querier { } // Params returns parameters of x/slashing module -func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { +func (k Keeper) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } - ctx := sdk.UnwrapSDKContext(c) - params := k.GetParams(ctx) + params, err := k.GetParams(ctx) - return &types.QueryParamsResponse{Params: params}, nil + return &types.QueryParamsResponse{Params: params}, err } // SigningInfo returns signing-info of a specific validator. -func (k Keeper) SigningInfo(c context.Context, req *types.QuerySigningInfoRequest) (*types.QuerySigningInfoResponse, error) { +func (k Keeper) SigningInfo(ctx context.Context, req *types.QuerySigningInfoRequest) (*types.QuerySigningInfoResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } @@ -50,9 +50,8 @@ func (k Keeper) SigningInfo(c context.Context, req *types.QuerySigningInfoReques return nil, err } - ctx := sdk.UnwrapSDKContext(c) - signingInfo, found := k.GetValidatorSigningInfo(ctx, consAddr) - if !found { + signingInfo, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err != nil { return nil, status.Errorf(codes.NotFound, "SigningInfo not found for validator %s", req.ConsAddress) } @@ -60,16 +59,15 @@ func (k Keeper) SigningInfo(c context.Context, req *types.QuerySigningInfoReques } // SigningInfos returns signing-infos of all validators. -func (k Keeper) SigningInfos(c context.Context, req *types.QuerySigningInfosRequest) (*types.QuerySigningInfosResponse, error) { +func (k Keeper) SigningInfos(ctx context.Context, req *types.QuerySigningInfosRequest) (*types.QuerySigningInfosResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } - ctx := sdk.UnwrapSDKContext(c) - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) var signInfos []types.ValidatorSigningInfo - sigInfoStore := prefix.NewStore(store, types.ValidatorSigningInfoKeyPrefix) + sigInfoStore := prefix.NewStore(runtime.KVStoreAdapter(store), types.ValidatorSigningInfoKeyPrefix) pageRes, err := query.Paginate(sigInfoStore, req.Pagination, func(key, value []byte) error { var info types.ValidatorSigningInfo err := k.cdc.Unmarshal(value, &info) diff --git a/x/slashing/keeper/grpc_query_test.go b/x/slashing/keeper/grpc_query_test.go index 630b9cc7191f..8048c85bec04 100644 --- a/x/slashing/keeper/grpc_query_test.go +++ b/x/slashing/keeper/grpc_query_test.go @@ -39,8 +39,8 @@ func (s *KeeperTestSuite) TestGRPCSigningInfo() { ) keeper.SetValidatorSigningInfo(ctx, consAddr, signingInfo) - info, found := keeper.GetValidatorSigningInfo(ctx, consAddr) - require.True(found) + info, err := keeper.GetValidatorSigningInfo(ctx, consAddr) + require.NoError(err) infoResp, err = queryClient.SigningInfo(gocontext.Background(), &slashingtypes.QuerySigningInfoRequest{ConsAddress: consAddr.String()}) diff --git a/x/slashing/keeper/hooks.go b/x/slashing/keeper/hooks.go index ede4480be7fd..0a307d1e72db 100644 --- a/x/slashing/keeper/hooks.go +++ b/x/slashing/keeper/hooks.go @@ -24,8 +24,8 @@ func (k Keeper) Hooks() Hooks { // AfterValidatorBonded updates the signing info start height or create a new signing info func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { - signingInfo, found := h.k.GetValidatorSigningInfo(ctx, consAddr) - if found { + signingInfo, err := h.k.GetValidatorSigningInfo(ctx, consAddr) + if err == nil { signingInfo.StartHeight = ctx.BlockHeight() } else { signingInfo = types.NewValidatorSigningInfo( @@ -38,15 +38,12 @@ func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, v ) } - h.k.SetValidatorSigningInfo(ctx, consAddr, signingInfo) - - return nil + return h.k.SetValidatorSigningInfo(ctx, consAddr, signingInfo) } // AfterValidatorRemoved deletes the address-pubkey relation when a validator is removed, func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, _ sdk.ValAddress) error { - h.k.deleteAddrPubkeyRelation(ctx, crypto.Address(consAddr)) - return nil + return h.k.deleteAddrPubkeyRelation(ctx, crypto.Address(consAddr)) } // AfterValidatorCreated adds the address-pubkey relation when a validator is created. diff --git a/x/slashing/keeper/hooks_test.go b/x/slashing/keeper/hooks_test.go index 9fe301699953..d9c592ffc15b 100644 --- a/x/slashing/keeper/hooks_test.go +++ b/x/slashing/keeper/hooks_test.go @@ -13,8 +13,8 @@ func (s *KeeperTestSuite) TestAfterValidatorBonded() { valAddr := sdk.ValAddress(consAddr.Bytes()) keeper.Hooks().AfterValidatorBonded(ctx, consAddr, valAddr) - _, ok := keeper.GetValidatorSigningInfo(ctx, consAddr) - require.True(ok) + _, err := keeper.GetValidatorSigningInfo(ctx, consAddr) + require.NoError(err) } func (s *KeeperTestSuite) TestAfterValidatorCreatedOrRemoved() { diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index c00f6e30be33..5a6fee86d6a4 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" "cosmossdk.io/core/comet" @@ -13,22 +14,28 @@ import ( ) // HandleValidatorSignature handles a validator signature, must be called once per validator per block. -func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Address, power int64, signed comet.BlockIDFlag) { +func (k Keeper) HandleValidatorSignature(ctx context.Context, addr cryptotypes.Address, power int64, signed comet.BlockIDFlag) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) logger := k.Logger(ctx) - height := ctx.BlockHeight() + height := sdkCtx.BlockHeight() // fetch the validator public key consAddr := sdk.ConsAddress(addr) // don't update missed blocks when validator's jailed - if k.sk.IsValidatorJailed(ctx, consAddr) { - return + if k.sk.IsValidatorJailed(sdkCtx, consAddr) { + return nil } // fetch signing info - signInfo, found := k.GetValidatorSigningInfo(ctx, consAddr) - if !found { - panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) + signInfo, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err != nil { + return err + } + + signedBlocksWindow, err := k.SignedBlocksWindow(ctx) + if err != nil { + return err } // Compute the relative index, so we count the blocks the validator *should* @@ -36,13 +43,13 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre // except for start height. The index is in the range [0, SignedBlocksWindow) // and is used to see if a validator signed a block at the given height, which // is represented by a bit in the bitmap. - index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx) + index := signInfo.IndexOffset % signedBlocksWindow signInfo.IndexOffset++ // determine if the validator signed the previous block previous, err := k.GetMissedBlockBitmapValue(ctx, consAddr, index) if err != nil { - panic(errors.Wrap(err, "failed to get the validator's bitmap value")) + return errors.Wrap(err, "failed to get the validator's bitmap value") } missed := signed == comet.BlockIDFlagAbsent @@ -51,7 +58,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre // Bitmap value has changed from not missed to missed, so we flip the bit // and increment the counter. if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, true); err != nil { - panic(err) + return err } signInfo.MissedBlocksCounter++ @@ -60,7 +67,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre // Bitmap value has changed from missed to not missed, so we flip the bit // and decrement the counter. if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, false); err != nil { - panic(err) + return err } signInfo.MissedBlocksCounter-- @@ -69,10 +76,13 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre // bitmap value at this index has not changed, no need to update counter } - minSignedPerWindow := k.MinSignedPerWindow(ctx) + minSignedPerWindow, err := k.MinSignedPerWindow(ctx) + if err != nil { + return err + } if missed { - ctx.EventManager().EmitEvent( + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeLiveness, sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), @@ -90,12 +100,12 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre ) } - minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) - maxMissed := k.SignedBlocksWindow(ctx) - minSignedPerWindow + minHeight := signInfo.StartHeight + signedBlocksWindow + maxMissed := signedBlocksWindow - minSignedPerWindow // if we are past the minimum height and the validator has missed too many blocks, punish them if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { - validator := k.sk.ValidatorByConsAddr(ctx, consAddr) + validator := k.sk.ValidatorByConsAddr(sdkCtx, consAddr) if validator != nil && !validator.IsJailed() { // Downtime confirmed: slash and jail the validator // We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height, @@ -105,8 +115,13 @@ 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.SlashWithInfractionReason(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx), stakingtypes.Infraction_INFRACTION_DOWNTIME) - ctx.EventManager().EmitEvent( + slashFractionDowntime, err := k.SlashFractionDowntime(ctx) + if err != nil { + return err + } + + coinsBurned := k.sk.SlashWithInfractionReason(sdkCtx, consAddr, distributionHeight, power, slashFractionDowntime, stakingtypes.Infraction_INFRACTION_DOWNTIME) + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSlash, sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), @@ -116,15 +131,22 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre sdk.NewAttribute(types.AttributeKeyBurnedCoins, coinsBurned.String()), ), ) - k.sk.Jail(ctx, consAddr) + k.sk.Jail(sdkCtx, consAddr) - signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeJailDuration(ctx)) + downtimeJailDur, err := k.DowntimeJailDuration(ctx) + if err != nil { + return err + } + signInfo.JailedUntil = sdkCtx.BlockHeader().Time.Add(downtimeJailDur) // We need to reset the counter & bitmap so that the validator won't be // immediately slashed for downtime upon re-bonding. signInfo.MissedBlocksCounter = 0 signInfo.IndexOffset = 0 - k.DeleteMissedBlockBitmap(ctx, consAddr) + err = k.DeleteMissedBlockBitmap(ctx, consAddr) + if err != nil { + return err + } logger.Info( "slashing and jailing validator due to liveness fault", @@ -132,7 +154,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre "validator", consAddr.String(), "min_height", minHeight, "threshold", minSignedPerWindow, - "slashed", k.SlashFractionDowntime(ctx).String(), + "slashed", slashFractionDowntime.String(), "jailed_until", signInfo.JailedUntil, ) } else { @@ -145,5 +167,5 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre } // Set the updated signing info - k.SetValidatorSigningInfo(ctx, consAddr, signInfo) + return k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } diff --git a/x/slashing/keeper/keeper.go b/x/slashing/keeper/keeper.go index 92e810eebd41..374f702f0675 100644 --- a/x/slashing/keeper/keeper.go +++ b/x/slashing/keeper/keeper.go @@ -1,12 +1,13 @@ package keeper import ( + "context" "fmt" "cosmossdk.io/log" sdkmath "cosmossdk.io/math" - storetypes "cosmossdk.io/store/types" + storetypes "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -17,10 +18,10 @@ import ( // Keeper of the slashing store type Keeper struct { - storeKey storetypes.StoreKey - cdc codec.BinaryCodec - legacyAmino *codec.LegacyAmino - sk types.StakingKeeper + storeService storetypes.KVStoreService + cdc codec.BinaryCodec + legacyAmino *codec.LegacyAmino + sk types.StakingKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. @@ -28,13 +29,13 @@ type Keeper struct { } // NewKeeper creates a slashing keeper -func NewKeeper(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key storetypes.StoreKey, sk types.StakingKeeper, authority string) Keeper { +func NewKeeper(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, storeService storetypes.KVStoreService, sk types.StakingKeeper, authority string) Keeper { return Keeper{ - storeKey: key, - cdc: cdc, - legacyAmino: legacyAmino, - sk: sk, - authority: authority, + storeService: storeService, + cdc: cdc, + legacyAmino: legacyAmino, + sk: sk, + authority: authority, } } @@ -44,26 +45,29 @@ func (k Keeper) GetAuthority() string { } // Logger returns a module-specific logger. -func (k Keeper) Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", "x/"+types.ModuleName) +func (k Keeper) Logger(ctx context.Context) log.Logger { + sdkCtx := sdk.UnwrapSDKContext(ctx) + return sdkCtx.Logger().With("module", "x/"+types.ModuleName) } // AddPubkey sets a address-pubkey relation -func (k Keeper) AddPubkey(ctx sdk.Context, pubkey cryptotypes.PubKey) error { +func (k Keeper) AddPubkey(ctx context.Context, pubkey cryptotypes.PubKey) error { bz, err := k.cdc.MarshalInterface(pubkey) if err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) key := types.AddrPubkeyRelationKey(pubkey.Address()) - store.Set(key, bz) - return nil + return store.Set(key, bz) } // GetPubkey returns the pubkey from the adddress-pubkey relation -func (k Keeper) GetPubkey(ctx sdk.Context, a cryptotypes.Address) (cryptotypes.PubKey, error) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.AddrPubkeyRelationKey(a)) +func (k Keeper) GetPubkey(ctx context.Context, a cryptotypes.Address) (cryptotypes.PubKey, error) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.AddrPubkeyRelationKey(a)) + if err != nil { + return nil, err + } if bz == nil { return nil, fmt.Errorf("address %s not found", sdk.ConsAddress(a)) } @@ -73,15 +77,16 @@ 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. It specifies no intraction reason. -func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, fraction sdkmath.LegacyDec, power, distributionHeight int64) { - k.SlashWithInfractionReason(ctx, consAddr, fraction, power, distributionHeight, stakingtypes.Infraction_INFRACTION_UNSPECIFIED) +func (k Keeper) Slash(ctx context.Context, consAddr sdk.ConsAddress, fraction sdkmath.LegacyDec, power, distributionHeight int64) error { + return k.SlashWithInfractionReason(ctx, consAddr, fraction, power, distributionHeight, stakingtypes.Infraction_INFRACTION_UNSPECIFIED) } // SlashWithInfractionReason attempts to slash a validator. The slash is delegated to the staking // module to make the necessary validator changes. It specifies an intraction reason. -func (k Keeper) SlashWithInfractionReason(ctx sdk.Context, consAddr sdk.ConsAddress, fraction sdkmath.LegacyDec, power, distributionHeight int64, infraction stakingtypes.Infraction) { - coinsBurned := k.sk.SlashWithInfractionReason(ctx, consAddr, distributionHeight, power, fraction, infraction) - ctx.EventManager().EmitEvent( +func (k Keeper) SlashWithInfractionReason(ctx context.Context, consAddr sdk.ConsAddress, fraction sdkmath.LegacyDec, power, distributionHeight int64, infraction stakingtypes.Infraction) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + coinsBurned := k.sk.SlashWithInfractionReason(sdkCtx, consAddr, distributionHeight, power, fraction, infraction) + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSlash, sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), @@ -90,21 +95,24 @@ func (k Keeper) SlashWithInfractionReason(ctx sdk.Context, consAddr sdk.ConsAddr sdk.NewAttribute(types.AttributeKeyBurnedCoins, coinsBurned.String()), ), ) + return nil } // Jail attempts to jail a validator. The slash is delegated to the staking module // to make the necessary validator changes. -func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { - k.sk.Jail(ctx, consAddr) - ctx.EventManager().EmitEvent( +func (k Keeper) Jail(ctx context.Context, consAddr sdk.ConsAddress) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + k.sk.Jail(sdkCtx, consAddr) + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSlash, sdk.NewAttribute(types.AttributeKeyJailed, consAddr.String()), ), ) + return nil } -func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr cryptotypes.Address) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.AddrPubkeyRelationKey(addr)) +func (k Keeper) deleteAddrPubkeyRelation(ctx context.Context, addr cryptotypes.Address) error { + store := k.storeService.OpenKVStore(ctx) + return store.Delete(types.AddrPubkeyRelationKey(addr)) } diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index 48ea926366d4..83f44bba3359 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -13,6 +13,7 @@ import ( storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/runtime" sdktestutil "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" @@ -39,6 +40,7 @@ type KeeperTestSuite struct { func (s *KeeperTestSuite) SetupTest() { key := storetypes.NewKVStoreKey(slashingtypes.StoreKey) + storeService := runtime.NewKVStoreService(key) testCtx := sdktestutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test")) ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()}) encCfg := moduletestutil.MakeTestEncodingConfig() @@ -51,7 +53,7 @@ func (s *KeeperTestSuite) SetupTest() { s.slashingKeeper = slashingkeeper.NewKeeper( encCfg.Codec, encCfg.Amino, - key, + storeService, s.stakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) @@ -79,18 +81,21 @@ func (s *KeeperTestSuite) TestPubkey() { } func (s *KeeperTestSuite) TestJailAndSlash() { + slashFractionDoubleSign, err := s.slashingKeeper.SlashFractionDoubleSign(s.ctx) + s.Require().NoError(err) + s.stakingKeeper.EXPECT().SlashWithInfractionReason(s.ctx, consAddr, s.ctx.BlockHeight(), sdk.TokensToConsensusPower(sdkmath.NewInt(1), sdk.DefaultPowerReduction), - s.slashingKeeper.SlashFractionDoubleSign(s.ctx), + slashFractionDoubleSign, stakingtypes.Infraction_INFRACTION_UNSPECIFIED, ).Return(sdkmath.NewInt(0)) s.slashingKeeper.Slash( s.ctx, consAddr, - s.slashingKeeper.SlashFractionDoubleSign(s.ctx), + slashFractionDoubleSign, sdk.TokensToConsensusPower(sdkmath.NewInt(1), sdk.DefaultPowerReduction), s.ctx.BlockHeight(), ) @@ -100,18 +105,21 @@ func (s *KeeperTestSuite) TestJailAndSlash() { } func (s *KeeperTestSuite) TestJailAndSlashWithInfractionReason() { + slashFractionDoubleSign, err := s.slashingKeeper.SlashFractionDoubleSign(s.ctx) + s.Require().NoError(err) + s.stakingKeeper.EXPECT().SlashWithInfractionReason(s.ctx, consAddr, s.ctx.BlockHeight(), sdk.TokensToConsensusPower(sdkmath.NewInt(1), sdk.DefaultPowerReduction), - s.slashingKeeper.SlashFractionDoubleSign(s.ctx), + slashFractionDoubleSign, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN, ).Return(sdkmath.NewInt(0)) s.slashingKeeper.SlashWithInfractionReason( s.ctx, consAddr, - s.slashingKeeper.SlashFractionDoubleSign(s.ctx), + slashFractionDoubleSign, sdk.TokensToConsensusPower(sdkmath.NewInt(1), sdk.DefaultPowerReduction), s.ctx.BlockHeight(), stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN, diff --git a/x/slashing/keeper/migrations.go b/x/slashing/keeper/migrations.go index 5fb281ccc6c0..356db7336f1a 100644 --- a/x/slashing/keeper/migrations.go +++ b/x/slashing/keeper/migrations.go @@ -1,6 +1,7 @@ package keeper import ( + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing/exported" v2 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v2" @@ -21,7 +22,7 @@ func NewMigrator(keeper Keeper, ss exported.Subspace) Migrator { // Migrate1to2 migrates from version 1 to 2. func (m Migrator) Migrate1to2(ctx sdk.Context) error { - return v2.MigrateStore(ctx, m.keeper.storeKey) + return v2.MigrateStore(ctx, m.keeper.storeService) } // Migrate2to3 migrates the x/slashing module state from the consensus @@ -29,12 +30,18 @@ func (m Migrator) Migrate1to2(ctx sdk.Context) error { // and managed by the x/params modules and stores them directly into the x/slashing // module state. func (m Migrator) Migrate2to3(ctx sdk.Context) error { - return v3.Migrate(ctx, ctx.KVStore(m.keeper.storeKey), m.legacySubspace, m.keeper.cdc) + store := runtime.KVStoreAdapter(m.keeper.storeService.OpenKVStore(ctx)) + return v3.Migrate(ctx, store, m.legacySubspace, m.keeper.cdc) } // Migrate3to4 migrates the x/slashing module state from the consensus // version 3 to version 4. Specifically, it migrates the validator missed block // bitmap. func (m Migrator) Migrate3to4(ctx sdk.Context) error { - return v4.Migrate(ctx, m.keeper.cdc, ctx.KVStore(m.keeper.storeKey), m.keeper.GetParams(ctx)) + store := runtime.KVStoreAdapter(m.keeper.storeService.OpenKVStore(ctx)) + params, err := m.keeper.GetParams(ctx) + if err != nil { + return err + } + return v4.Migrate(ctx, m.keeper.cdc, store, params) } diff --git a/x/slashing/keeper/params.go b/x/slashing/keeper/params.go index 77371845a696..5fb7fac627d2 100644 --- a/x/slashing/keeper/params.go +++ b/x/slashing/keeper/params.go @@ -1,61 +1,75 @@ package keeper import ( + "context" "time" sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing/types" ) // SignedBlocksWindow - sliding window for downtime slashing -func (k Keeper) SignedBlocksWindow(ctx sdk.Context) (res int64) { - return k.GetParams(ctx).SignedBlocksWindow +func (k Keeper) SignedBlocksWindow(ctx context.Context) (int64, error) { + params, err := k.GetParams(ctx) + return params.SignedBlocksWindow, err } // MinSignedPerWindow - minimum blocks signed per window -func (k Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { - params := k.GetParams(ctx) - signedBlocksWindow := k.SignedBlocksWindow(ctx) +func (k Keeper) MinSignedPerWindow(ctx context.Context) (int64, error) { + params, err := k.GetParams(ctx) + if err != nil { + return 0, err + } + + signedBlocksWindow := params.SignedBlocksWindow + minSignedPerWindow := params.MinSignedPerWindow // NOTE: RoundInt64 will never panic as minSignedPerWindow is // less than 1. - return params.MinSignedPerWindow.MulInt64(signedBlocksWindow).RoundInt64() + return minSignedPerWindow.MulInt64(signedBlocksWindow).RoundInt64(), nil } // DowntimeJailDuration - Downtime unbond duration -func (k Keeper) DowntimeJailDuration(ctx sdk.Context) (res time.Duration) { - return k.GetParams(ctx).DowntimeJailDuration +func (k Keeper) DowntimeJailDuration(ctx context.Context) (time.Duration, error) { + params, err := k.GetParams(ctx) + return params.DowntimeJailDuration, err } // SlashFractionDoubleSign - fraction of power slashed in case of double sign -func (k Keeper) SlashFractionDoubleSign(ctx sdk.Context) (res sdkmath.LegacyDec) { - return k.GetParams(ctx).SlashFractionDoubleSign +func (k Keeper) SlashFractionDoubleSign(ctx context.Context) (sdkmath.LegacyDec, error) { + params, err := k.GetParams(ctx) + return params.SlashFractionDoubleSign, err } // SlashFractionDowntime - fraction of power slashed for downtime -func (k Keeper) SlashFractionDowntime(ctx sdk.Context) (res sdkmath.LegacyDec) { - return k.GetParams(ctx).SlashFractionDowntime +func (k Keeper) SlashFractionDowntime(ctx context.Context) (sdkmath.LegacyDec, error) { + params, err := k.GetParams(ctx) + return params.SlashFractionDowntime, err } // GetParams returns the current x/slashing module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (params types.Params, err error) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + return params, err + } if bz == nil { - return params + return params, nil } - k.cdc.MustUnmarshal(bz, ¶ms) - return params + + err = k.cdc.Unmarshal(bz, ¶ms) + return params, err } // SetParams sets the x/slashing module parameters. // CONTRACT: This method performs no validation of the parameters. -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshal(¶ms) - store.Set(types.ParamsKey, bz) - - return nil +func (k Keeper) SetParams(ctx context.Context, params types.Params) error { + store := k.storeService.OpenKVStore(ctx) + bz, err := k.cdc.Marshal(¶ms) + if err != nil { + return err + } + return store.Set(types.ParamsKey, bz) } diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index 628b262369ea..7aec8e50cae1 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -1,94 +1,111 @@ package keeper import ( + "context" "time" + "cosmossdk.io/errors" storetypes "cosmossdk.io/store/types" "github.com/bits-and-blooms/bitset" - "github.com/cockroachdb/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing/types" ) // GetValidatorSigningInfo retruns the ValidatorSigningInfo for a specific validator -// ConsAddress -func (k Keeper) GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (types.ValidatorSigningInfo, bool) { - store := ctx.KVStore(k.storeKey) - +// ConsAddress. If not found it returns ErrNoSigningInfoFound, but other errors +// may be returned if there is an error reading from the store. +func (k Keeper) GetValidatorSigningInfo(ctx context.Context, address sdk.ConsAddress) (types.ValidatorSigningInfo, error) { + store := k.storeService.OpenKVStore(ctx) var info types.ValidatorSigningInfo - bz := store.Get(types.ValidatorSigningInfoKey(address)) + bz, err := store.Get(types.ValidatorSigningInfoKey(address)) + if err != nil { + return info, err + } + if bz == nil { - return info, false + return info, types.ErrNoSigningInfoFound } - k.cdc.MustUnmarshal(bz, &info) - return info, true + err = k.cdc.Unmarshal(bz, &info) + return info, err } // HasValidatorSigningInfo returns if a given validator has signing information // persisted. -func (k Keeper) HasValidatorSigningInfo(ctx sdk.Context, consAddr sdk.ConsAddress) bool { - _, ok := k.GetValidatorSigningInfo(ctx, consAddr) - return ok +func (k Keeper) HasValidatorSigningInfo(ctx context.Context, consAddr sdk.ConsAddress) bool { + _, err := k.GetValidatorSigningInfo(ctx, consAddr) + return err == nil } // SetValidatorSigningInfo sets the validator signing info to a consensus address key -func (k Keeper) SetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info types.ValidatorSigningInfo) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshal(&info) - store.Set(types.ValidatorSigningInfoKey(address), bz) +func (k Keeper) SetValidatorSigningInfo(ctx context.Context, address sdk.ConsAddress, info types.ValidatorSigningInfo) error { + store := k.storeService.OpenKVStore(ctx) + bz, err := k.cdc.Marshal(&info) + if err != nil { + return err + } + + return store.Set(types.ValidatorSigningInfoKey(address), bz) } // IterateValidatorSigningInfos iterates over the stored ValidatorSigningInfo -func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, +func (k Keeper) IterateValidatorSigningInfos(ctx context.Context, handler func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool), -) { - store := ctx.KVStore(k.storeKey) - iter := storetypes.KVStorePrefixIterator(store, types.ValidatorSigningInfoKeyPrefix) +) error { + store := k.storeService.OpenKVStore(ctx) + iter, err := store.Iterator(types.ValidatorSigningInfoKeyPrefix, storetypes.PrefixEndBytes(types.ValidatorSigningInfoKeyPrefix)) + if err != nil { + return err + } defer iter.Close() for ; iter.Valid(); iter.Next() { address := types.ValidatorSigningInfoAddress(iter.Key()) var info types.ValidatorSigningInfo - k.cdc.MustUnmarshal(iter.Value(), &info) + err = k.cdc.Unmarshal(iter.Value(), &info) + if err != nil { + return err + } + if handler(address, info) { break } } + return nil } // JailUntil attempts to set a validator's JailedUntil attribute in its signing // info. It will panic if the signing info does not exist for the validator. -func (k Keeper) JailUntil(ctx sdk.Context, consAddr sdk.ConsAddress, jailTime time.Time) { - signInfo, ok := k.GetValidatorSigningInfo(ctx, consAddr) - if !ok { - panic("cannot jail validator that does not have any signing information") +func (k Keeper) JailUntil(ctx context.Context, consAddr sdk.ConsAddress, jailTime time.Time) error { + signInfo, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err != nil { + return errors.Wrap(err, "cannot jail validator that does not have any signing information") } signInfo.JailedUntil = jailTime - k.SetValidatorSigningInfo(ctx, consAddr, signInfo) + return k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } // Tombstone attempts to tombstone a validator. It will panic if signing info for // the given validator does not exist. -func (k Keeper) Tombstone(ctx sdk.Context, consAddr sdk.ConsAddress) { - signInfo, ok := k.GetValidatorSigningInfo(ctx, consAddr) - if !ok { - panic("cannot tombstone validator that does not have any signing information") +func (k Keeper) Tombstone(ctx context.Context, consAddr sdk.ConsAddress) error { + signInfo, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err != nil { + return types.ErrNoSigningInfoFound.Wrap("cannot tombstone validator that does not have any signing information") } if signInfo.Tombstoned { - panic("cannot tombstone validator that is already tombstoned") + return types.ErrValidatorTombstoned.Wrap("cannot tombstone validator that is already tombstoned") } signInfo.Tombstoned = true - k.SetValidatorSigningInfo(ctx, consAddr, signInfo) + return k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } // IsTombstoned returns if a given validator by consensus address is tombstoned. -func (k Keeper) IsTombstoned(ctx sdk.Context, consAddr sdk.ConsAddress) bool { - signInfo, ok := k.GetValidatorSigningInfo(ctx, consAddr) - if !ok { +func (k Keeper) IsTombstoned(ctx context.Context, consAddr sdk.ConsAddress) bool { + signInfo, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err != nil { return false } @@ -97,18 +114,18 @@ func (k Keeper) IsTombstoned(ctx sdk.Context, consAddr sdk.ConsAddress) bool { // getMissedBlockBitmapChunk gets the bitmap chunk at the given chunk index for // a validator's missed block signing window. -func (k Keeper) getMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex int64) []byte { - store := ctx.KVStore(k.storeKey) - chunk := store.Get(types.ValidatorMissedBlockBitmapKey(addr, chunkIndex)) - return chunk +func (k Keeper) getMissedBlockBitmapChunk(ctx context.Context, addr sdk.ConsAddress, chunkIndex int64) ([]byte, error) { + store := k.storeService.OpenKVStore(ctx) + chunk, err := store.Get(types.ValidatorMissedBlockBitmapKey(addr, chunkIndex)) + return chunk, err } // setMissedBlockBitmapChunk sets the bitmap chunk at the given chunk index for // a validator's missed block signing window. -func (k Keeper) setMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex int64, chunk []byte) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) setMissedBlockBitmapChunk(ctx context.Context, addr sdk.ConsAddress, chunkIndex int64, chunk []byte) error { + store := k.storeService.OpenKVStore(ctx) key := types.ValidatorMissedBlockBitmapKey(addr, chunkIndex) - store.Set(key, chunk) + return store.Set(key, chunk) } // GetMissedBlockBitmapValue returns true if a validator missed signing a block @@ -117,12 +134,16 @@ func (k Keeper) setMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, // where each bit represents a height, and is determined by the validator's // IndexOffset modulo SignedBlocksWindow. This index is used to fetch the chunk // in the bitmap and the relative bit in that chunk. -func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64) (bool, error) { +func (k Keeper) GetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddress, index int64) (bool, error) { // get the chunk or "word" in the logical bitmap chunkIndex := index / types.MissedBlockBitmapChunkSize bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) - chunk := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex) + chunk, err := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex) + if err != nil { + return false, errors.Wrapf(err, "failed to get bitmap chunk; index: %d", index) + } + if chunk != nil { if err := bs.UnmarshalBinary(chunk); err != nil { return false, errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) @@ -142,12 +163,16 @@ func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, // determined by the validator's IndexOffset modulo SignedBlocksWindow. This // index is used to fetch the chunk in the bitmap and the relative bit in that // chunk. -func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64, missed bool) error { +func (k Keeper) SetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddress, index int64, missed bool) error { // get the chunk or "word" in the logical bitmap chunkIndex := index / types.MissedBlockBitmapChunkSize bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) - chunk := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex) + chunk, err := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex) + if err != nil { + return errors.Wrapf(err, "failed to get bitmap chunk; index: %d", index) + } + if chunk != nil { if err := bs.UnmarshalBinary(chunk); err != nil { return errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) @@ -172,15 +197,22 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, } // DeleteMissedBlockBitmap removes a validator's missed block bitmap from state. -func (k Keeper) DeleteMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress) { - store := ctx.KVStore(k.storeKey) - - iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitmapPrefixKey(addr)) +func (k Keeper) DeleteMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddress) error { + store := k.storeService.OpenKVStore(ctx) + prefix := types.ValidatorMissedBlockBitmapPrefixKey(addr) + iter, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + if err != nil { + return err + } defer iter.Close() for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) + err = store.Delete(iter.Key()) + if err != nil { + return err + } } + return nil } // IterateMissedBlockBitmap iterates over a validator's signed blocks window @@ -189,10 +221,13 @@ func (k Keeper) DeleteMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress) { // // Note: A callback will only be executed over all bitmap chunks that exist in // state. -func (k Keeper) IterateMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress, cb func(index int64, missed bool) (stop bool)) { - store := ctx.KVStore(k.storeKey) - - iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitmapPrefixKey(addr)) +func (k Keeper) IterateMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddress, cb func(index int64, missed bool) (stop bool)) error { + store := k.storeService.OpenKVStore(ctx) + prefix := types.ValidatorMissedBlockBitmapPrefixKey(addr) + iter, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + if err != nil { + return err + } defer iter.Close() var index int64 @@ -200,7 +235,7 @@ func (k Keeper) IterateMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress, bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) if err := bs.UnmarshalBinary(iter.Value()); err != nil { - panic(errors.Wrapf(err, "failed to decode bitmap chunk; index: %v", string(iter.Key()))) + return errors.Wrapf(err, "failed to decode bitmap chunk; index: %v", string(iter.Key())) } for i := uint(0); i < types.MissedBlockBitmapChunkSize; i++ { @@ -212,12 +247,18 @@ func (k Keeper) IterateMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress, index++ } } + return nil } // GetValidatorMissedBlocks returns array of missed blocks for given validator. -func (k Keeper) GetValidatorMissedBlocks(ctx sdk.Context, addr sdk.ConsAddress) []types.MissedBlock { - missedBlocks := make([]types.MissedBlock, 0, k.SignedBlocksWindow(ctx)) - k.IterateMissedBlockBitmap(ctx, addr, func(index int64, missed bool) (stop bool) { +func (k Keeper) GetValidatorMissedBlocks(ctx context.Context, addr sdk.ConsAddress) ([]types.MissedBlock, error) { + signedBlocksWindow, err := k.SignedBlocksWindow(ctx) + if err != nil { + return nil, err + } + + missedBlocks := make([]types.MissedBlock, 0, signedBlocksWindow) + err = k.IterateMissedBlockBitmap(ctx, addr, func(index int64, missed bool) (stop bool) { if missed { missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed)) } @@ -225,5 +266,5 @@ func (k Keeper) GetValidatorMissedBlocks(ctx sdk.Context, addr sdk.ConsAddress) return false }) - return missedBlocks + return missedBlocks, err } diff --git a/x/slashing/keeper/signing_info_test.go b/x/slashing/keeper/signing_info_test.go index c08e59f26520..c24ece21c6ec 100644 --- a/x/slashing/keeper/signing_info_test.go +++ b/x/slashing/keeper/signing_info_test.go @@ -26,8 +26,8 @@ func (s *KeeperTestSuite) TestValidatorSigningInfo() { keeper.SetValidatorSigningInfo(ctx, consAddr, signingInfo) require.True(keeper.HasValidatorSigningInfo(ctx, consAddr)) - info, found := keeper.GetValidatorSigningInfo(ctx, consAddr) - require.True(found) + info, err := keeper.GetValidatorSigningInfo(ctx, consAddr) + require.NoError(err) require.Equal(info.StartHeight, ctx.BlockHeight()) require.Equal(info.IndexOffset, int64(3)) require.Equal(info.JailedUntil, time.Unix(2, 0).UTC()) @@ -43,7 +43,8 @@ func (s *KeeperTestSuite) TestValidatorSigningInfo() { require.Equal(signingInfos[0].Address, signingInfo.Address) // test Tombstone - keeper.Tombstone(ctx, consAddr) + err = keeper.Tombstone(ctx, consAddr) + require.NoError(err) require.True(keeper.IsTombstoned(ctx, consAddr)) // test JailUntil @@ -77,12 +78,13 @@ func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap_SmallWindow() { } // validator should have missed all blocks - missedBlocks := keeper.GetValidatorMissedBlocks(ctx, consAddr) + missedBlocks, err := keeper.GetValidatorMissedBlocks(ctx, consAddr) + require.NoError(err) require.Len(missedBlocks, int(params.SignedBlocksWindow)) // sign next block, which rolls the missed block bitmap idx := valIdxOffset % params.SignedBlocksWindow - err := keeper.SetMissedBlockBitmapValue(ctx, consAddr, idx, false) + err = keeper.SetMissedBlockBitmapValue(ctx, consAddr, idx, false) require.NoError(err) missed, err := keeper.GetMissedBlockBitmapValue(ctx, consAddr, idx) @@ -90,7 +92,8 @@ func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap_SmallWindow() { require.False(missed) // validator should have missed all blocks except the last one - missedBlocks = keeper.GetValidatorMissedBlocks(ctx, consAddr) + missedBlocks, err = keeper.GetValidatorMissedBlocks(ctx, consAddr) + require.NoError(err) require.Len(missedBlocks, int(params.SignedBlocksWindow)-1) } } diff --git a/x/slashing/keeper/unjail.go b/x/slashing/keeper/unjail.go index d34cf719de40..92168dc2c76a 100644 --- a/x/slashing/keeper/unjail.go +++ b/x/slashing/keeper/unjail.go @@ -1,6 +1,8 @@ package keeper import ( + "context" + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -9,14 +11,15 @@ import ( // Unjail calls the staking Unjail function to unjail a validator if the // jailed period has concluded -func (k Keeper) Unjail(ctx sdk.Context, validatorAddr sdk.ValAddress) error { - validator := k.sk.Validator(ctx, validatorAddr) +func (k Keeper) Unjail(ctx context.Context, validatorAddr sdk.ValAddress) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + validator := k.sk.Validator(sdkCtx, validatorAddr) if validator == nil { return types.ErrNoValidatorForAddress } // cannot be unjailed if no self-delegation exists - selfDel := k.sk.Delegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + selfDel := k.sk.Delegation(sdkCtx, sdk.AccAddress(validatorAddr), validatorAddr) if selfDel == nil { return types.ErrMissingSelfDelegation } @@ -46,19 +49,19 @@ func (k Keeper) Unjail(ctx sdk.Context, validatorAddr sdk.ValAddress) error { // that the validator was never bonded and must've been jailed due to falling // below their minimum self-delegation. The validator can unjail at any point // assuming they've now bonded above their minimum self-delegation. - info, found := k.GetValidatorSigningInfo(ctx, consAddr) - if found { + info, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err == nil { // cannot be unjailed if tombstoned if info.Tombstoned { return types.ErrValidatorJailed } // cannot be unjailed until out of jail - if ctx.BlockHeader().Time.Before(info.JailedUntil) { + if sdkCtx.BlockHeader().Time.Before(info.JailedUntil) { return types.ErrValidatorJailed } } - k.sk.Unjail(ctx, consAddr) + k.sk.Unjail(sdkCtx, consAddr) return nil } diff --git a/x/slashing/migrations/v2/store.go b/x/slashing/migrations/v2/store.go index 86e2df55b30d..6d1179ea12da 100644 --- a/x/slashing/migrations/v2/store.go +++ b/x/slashing/migrations/v2/store.go @@ -1,9 +1,11 @@ package v2 import ( - storetypes "cosmossdk.io/store/types" + "context" - sdk "github.com/cosmos/cosmos-sdk/types" + storetypes "cosmossdk.io/core/store" + + "github.com/cosmos/cosmos-sdk/runtime" v2distribution "github.com/cosmos/cosmos-sdk/x/distribution/migrations/v2" v1 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v1" ) @@ -12,8 +14,8 @@ import ( // migration includes: // // - Change addresses to be length-prefixed. -func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey) error { - store := ctx.KVStore(storeKey) +func MigrateStore(ctx context.Context, storeService storetypes.KVStoreService) error { + store := runtime.KVStoreAdapter(storeService.OpenKVStore(ctx)) v2distribution.MigratePrefixAddress(store, v1.ValidatorSigningInfoKeyPrefix) v2distribution.MigratePrefixAddressBytes(store, v1.ValidatorMissedBlockBitArrayKeyPrefix) v2distribution.MigratePrefixAddress(store, v1.AddrPubkeyRelationKeyPrefix) diff --git a/x/slashing/migrations/v2/store_test.go b/x/slashing/migrations/v2/store_test.go index da583b79e6d4..c1f7f0fec6d1 100644 --- a/x/slashing/migrations/v2/store_test.go +++ b/x/slashing/migrations/v2/store_test.go @@ -7,6 +7,7 @@ import ( storetypes "cosmossdk.io/store/types" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" @@ -18,7 +19,8 @@ import ( func TestStoreMigration(t *testing.T) { slashingKey := storetypes.NewKVStoreKey("slashing") ctx := testutil.DefaultContext(slashingKey, storetypes.NewTransientStoreKey("transient_test")) - store := ctx.KVStore(slashingKey) + storeService := runtime.NewKVStoreService(slashingKey) + store := storeService.OpenKVStore(ctx) _, _, addr1 := testdata.KeyTestPubAddr() consAddr := sdk.ConsAddress(addr1) @@ -49,11 +51,12 @@ func TestStoreMigration(t *testing.T) { // Set all the old keys to the store for _, tc := range testCases { - store.Set(tc.oldKey, value) + err := store.Set(tc.oldKey, value) + require.NoError(t, err) } // Run migrations. - err := v2.MigrateStore(ctx, slashingKey) + err := v2.MigrateStore(ctx, storeService) require.NoError(t, err) // Make sure the new keys are set and old keys are deleted. @@ -61,9 +64,13 @@ func TestStoreMigration(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { if !bytes.Equal(tc.oldKey, tc.newKey) { - require.Nil(t, store.Get(tc.oldKey)) + v, err := store.Get(tc.oldKey) + require.NoError(t, err) + require.Nil(t, v) } - require.Equal(t, value, store.Get(tc.newKey)) + v, err := store.Get(tc.newKey) + require.NoError(t, err) + require.Equal(t, value, v) }) } } diff --git a/x/slashing/module.go b/x/slashing/module.go index 654c7b57fe70..b953b83ecaeb 100644 --- a/x/slashing/module.go +++ b/x/slashing/module.go @@ -7,8 +7,8 @@ import ( modulev1 "cosmossdk.io/api/cosmos/slashing/module/v1" "cosmossdk.io/core/appmodule" + store "cosmossdk.io/core/store" "cosmossdk.io/depinject" - store "cosmossdk.io/store/types" abci "github.com/cometbft/cometbft/abci/types" gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -183,9 +183,7 @@ func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // BeginBlock returns the begin blocker for the slashing module. func (am AppModule) BeginBlock(ctx context.Context) error { - c := sdk.UnwrapSDKContext(ctx) - BeginBlocker(c, am.keeper) - return nil + return BeginBlocker(ctx, am.keeper) } // AppModuleSimulation functions @@ -227,11 +225,11 @@ func init() { type ModuleInputs struct { depinject.In - Config *modulev1.Module - Key *store.KVStoreKey - Cdc codec.Codec - LegacyAmino *codec.LegacyAmino - Registry cdctypes.InterfaceRegistry + Config *modulev1.Module + StoreService store.KVStoreService + Cdc codec.Codec + LegacyAmino *codec.LegacyAmino + Registry cdctypes.InterfaceRegistry AccountKeeper types.AccountKeeper BankKeeper types.BankKeeper @@ -256,7 +254,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) } - k := keeper.NewKeeper(in.Cdc, in.LegacyAmino, in.Key, in.StakingKeeper, authority.String()) + k := keeper.NewKeeper(in.Cdc, in.LegacyAmino, in.StoreService, in.StakingKeeper, authority.String()) m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.StakingKeeper, in.LegacySubspace, in.Registry) return ModuleOutputs{ Keeper: k, diff --git a/x/slashing/simulation/operations.go b/x/slashing/simulation/operations.go index d703713e803a..161d6a40c0f4 100644 --- a/x/slashing/simulation/operations.go +++ b/x/slashing/simulation/operations.go @@ -84,9 +84,9 @@ func SimulateMsgUnjail( if err != nil { return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validator consensus key"), nil, err } - info, found := k.GetValidatorSigningInfo(ctx, consAddr) - if !found { - return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find validator signing info"), nil, nil // skip + info, err := k.GetValidatorSigningInfo(ctx, consAddr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find validator signing info"), nil, err // skip } selfDel := sk.Delegation(ctx, simAccount.Address, validator.GetOperator()) diff --git a/x/slashing/types/errors.go b/x/slashing/types/errors.go index 8e710779881f..2fb1f5e75649 100644 --- a/x/slashing/types/errors.go +++ b/x/slashing/types/errors.go @@ -11,4 +11,5 @@ var ( ErrMissingSelfDelegation = errors.Register(ModuleName, 6, "validator has no self-delegation; cannot be unjailed") ErrSelfDelegationTooLowToUnjail = errors.Register(ModuleName, 7, "validator's self delegation less than minimum; cannot be unjailed") ErrNoSigningInfoFound = errors.Register(ModuleName, 8, "no validator signing info found") + ErrValidatorTombstoned = errors.Register(ModuleName, 9, "validator already tombstoned") )