diff --git a/.pending/improvements/sdk/4717-refactor-slashi b/.pending/improvements/sdk/4717-refactor-slashi new file mode 100644 index 000000000..24a2359d6 --- /dev/null +++ b/.pending/improvements/sdk/4717-refactor-slashi @@ -0,0 +1 @@ +#4717 refactor `x/slashing` to match the new module spec \ No newline at end of file diff --git a/docs/spec/slashing/03_messages.md b/docs/spec/slashing/03_messages.md index ce9c1af94..ca19c892a 100644 --- a/docs/spec/slashing/03_messages.md +++ b/docs/spec/slashing/03_messages.md @@ -21,7 +21,7 @@ handleMsgUnjail(tx TxUnjail) if !validator.Jailed fail with "Validator not jailed, cannot unjail" - info = getValidatorSigningInfo(operator) + info = GetValidatorSigningInfo(operator) if info.Tombstoned fail with "Tombstoned validator cannot be unjailed" if block time < info.JailedUntil diff --git a/docs/spec/slashing/05_hooks.md b/docs/spec/slashing/05_hooks.md index 74207891f..4963142ee 100644 --- a/docs/spec/slashing/05_hooks.md +++ b/docs/spec/slashing/05_hooks.md @@ -10,7 +10,7 @@ now-bonded validator, which `StartHeight` of the current block. ``` onValidatorBonded(address sdk.ValAddress) - signingInfo, found = getValidatorSigningInfo(address) + signingInfo, found = GetValidatorSigningInfo(address) if !found { signingInfo = ValidatorSigningInfo { StartHeight : CurrentHeight, diff --git a/x/params/alias.go b/x/params/alias.go index be51ad53d..60ecdafdf 100644 --- a/x/params/alias.go +++ b/x/params/alias.go @@ -25,6 +25,7 @@ const ( var ( // functions aliases + NewParamSetPair = subspace.NewParamSetPair NewSubspace = subspace.NewSubspace NewKeyTable = subspace.NewKeyTable DefaultTestComponents = subspace.DefaultTestComponents diff --git a/x/params/subspace/paramset.go b/x/params/subspace/paramset.go index 66ebf5366..8f2206217 100644 --- a/x/params/subspace/paramset.go +++ b/x/params/subspace/paramset.go @@ -1,15 +1,20 @@ package subspace -// Used for associating paramsubspace key and field of param structs +// ParamSetPair is used for associating paramsubspace key and field of param structs type ParamSetPair struct { Key []byte Value interface{} } -// Slice of KeyFieldPair +// NewParamSetPair creates a new ParamSetPair instance +func NewParamSetPair(key []byte, value interface{}) ParamSetPair { + return ParamSetPair{key, value} +} + +// ParamSetPairs Slice of KeyFieldPair type ParamSetPairs []ParamSetPair -// Interface for structs containing parameters for a module +// ParamSet defines an interface for structs containing parameters for a module type ParamSet interface { ParamSetPairs() ParamSetPairs } diff --git a/x/slashing/abci.go b/x/slashing/abci.go index ef21e4477..74931763f 100644 --- a/x/slashing/abci.go +++ b/x/slashing/abci.go @@ -9,13 +9,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// slashing begin block functionality -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) { +// BeginBlocker check for infraction evidence or downtime of validators +// on every begin block +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { // 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 req.LastCommitInfo.GetVotes() { - sk.HandleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) + k.HandleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) } // Iterate through any newly discovered evidence of infraction @@ -24,9 +25,9 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) { for _, evidence := range req.ByzantineValidators { switch evidence.Type { case tmtypes.ABCIEvidenceTypeDuplicateVote: - sk.HandleDoubleSign(ctx, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power) + k.HandleDoubleSign(ctx, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power) default: - sk.Logger(ctx).Error(fmt.Sprintf("ignored unknown evidence type: %s", evidence.Type)) + k.Logger(ctx).Error(fmt.Sprintf("ignored unknown evidence type: %s", evidence.Type)) } } } diff --git a/x/slashing/abci_test.go b/x/slashing/abci_test.go index 5edea6309..84d36c6ee 100644 --- a/x/slashing/abci_test.go +++ b/x/slashing/abci_test.go @@ -9,22 +9,23 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/internal/keeper" "github.com/cosmos/cosmos-sdk/x/staking" ) func TestBeginBlocker(t *testing.T) { - ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + ctx, ck, sk, _, keeper := slashingkeeper.CreateTestInput(t, DefaultParams()) power := int64(100) amt := sdk.TokensFromConsensusPower(power) - addr, pk := addrs[2], pks[2] + addr, pk := slashingkeeper.Addrs[2], slashingkeeper.Pks[2] // bond the validator - got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) + got := staking.NewHandler(sk)(ctx, slashingkeeper.NewTestMsgCreateValidator(addr, pk, amt)) require.True(t, got.IsOK()) staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), - sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, slashingkeeper.InitTokens.Sub(amt))), ) require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) @@ -44,7 +45,7 @@ func TestBeginBlocker(t *testing.T) { } BeginBlocker(ctx, req, keeper) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) + info, found := keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) require.True(t, found) require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, int64(1), info.IndexOffset) diff --git a/x/slashing/alias.go b/x/slashing/alias.go index 7eb169c29..4d28dc09b 100644 --- a/x/slashing/alias.go +++ b/x/slashing/alias.go @@ -1,11 +1,13 @@ // nolint // autogenerated code using github.com/rigelrozanski/multitool // aliases generated for the following subdirectories: -// ALIASGEN: github.com/cosmos/cosmos-sdk/x/slashing/types +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/slashing/internal/keeper +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/slashing/internal/types package slashing import ( - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/keeper" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) const ( @@ -20,17 +22,19 @@ const ( StoreKey = types.StoreKey RouterKey = types.RouterKey QuerierRoute = types.QuerierRoute - QueryParameters = types.QueryParameters - QuerySigningInfo = types.QuerySigningInfo - QuerySigningInfos = types.QuerySigningInfos DefaultParamspace = types.DefaultParamspace DefaultMaxEvidenceAge = types.DefaultMaxEvidenceAge DefaultSignedBlocksWindow = types.DefaultSignedBlocksWindow DefaultDowntimeJailDuration = types.DefaultDowntimeJailDuration + QueryParameters = types.QueryParameters + QuerySigningInfo = types.QuerySigningInfo + QuerySigningInfos = types.QuerySigningInfos ) var ( // functions aliases + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier RegisterCodec = types.RegisterCodec ErrNoValidatorForAddress = types.ErrNoValidatorForAddress ErrBadValidatorAddr = types.ErrBadValidatorAddr @@ -40,6 +44,7 @@ var ( ErrSelfDelegationTooLowToUnjail = types.ErrSelfDelegationTooLowToUnjail ErrNoSigningInfoFound = types.ErrNoSigningInfoFound NewGenesisState = types.NewGenesisState + NewMissedBlock = types.NewMissedBlock DefaultGenesisState = types.DefaultGenesisState ValidateGenesis = types.ValidateGenesis GetValidatorSigningInfoKey = types.GetValidatorSigningInfoKey @@ -73,6 +78,8 @@ var ( ) type ( + Hooks = keeper.Hooks + Keeper = keeper.Keeper CodeType = types.CodeType GenesisState = types.GenesisState MissedBlock = types.MissedBlock diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index acdc1e7e9..2cf7704fa 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -1,3 +1,5 @@ +// nolint +// DONTCOVER package slashing import ( @@ -97,7 +99,7 @@ func checkValidator(t *testing.T, mapp *mock.App, keeper staking.Keeper, func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, addr sdk.ConsAddress, expFound bool) ValidatorSigningInfo { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) + signingInfo, found := keeper.GetValidatorSigningInfo(ctxCheck, addr) require.Equal(t, expFound, found) return signingInfo } diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 07b55458b..fabdb1485 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -11,7 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) // GetQueryCmd returns the cli query commands for this module diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index c8967e482..32ed99131 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -9,7 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) // GetTxCmd returns the transaction commands for this module diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index 30a3e6683..2c1c84593 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 16186d35d..bd7188a1c 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -10,7 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index c39010c67..8b959c8a0 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -2,7 +2,7 @@ package slashing import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" "github.com/cosmos/cosmos-sdk/x/staking/exported" ) @@ -11,7 +11,7 @@ import ( func InitGenesis(ctx sdk.Context, keeper Keeper, stakingKeeper types.StakingKeeper, data types.GenesisState) { stakingKeeper.IterateValidators(ctx, func(index int64, validator exported.ValidatorI) bool { - keeper.addPubkey(ctx, validator.GetConsPubKey()) + keeper.AddPubkey(ctx, validator.GetConsPubKey()) return false }, ) @@ -30,20 +30,18 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, stakingKeeper types.StakingKeep panic(err) } for _, missed := range array { - keeper.setValidatorMissedBlockBitArray(ctx, address, missed.Index, missed.Missed) + keeper.SetValidatorMissedBlockBitArray(ctx, address, missed.Index, missed.Missed) } } - keeper.paramspace.SetParamSet(ctx, &data.Params) + keeper.SetParams(ctx, data.Params) } // ExportGenesis writes the current store values // to a genesis file, which can be imported again // with InitGenesis func ExportGenesis(ctx sdk.Context, keeper Keeper) (data types.GenesisState) { - var params types.Params - keeper.paramspace.GetParamSet(ctx, ¶ms) - + params := keeper.GetParams(ctx) signingInfos := make(map[string]types.ValidatorSigningInfo) missedBlocks := make(map[string][]types.MissedBlock) keeper.IterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) { @@ -52,7 +50,7 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data types.GenesisState) { localMissedBlocks := []types.MissedBlock{} keeper.IterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { - localMissedBlocks = append(localMissedBlocks, types.MissedBlock{index, missed}) + localMissedBlocks = append(localMissedBlocks, types.NewMissedBlock(index, missed)) return false }) missedBlocks[bechAddr] = localMissedBlocks @@ -60,9 +58,5 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data types.GenesisState) { return false }) - return types.GenesisState{ - Params: params, - SigningInfos: signingInfos, - MissedBlocks: missedBlocks, - } + return types.NewGenesisState(params, signingInfos, missedBlocks) } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index be7885885..fa66e3ce8 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -4,9 +4,10 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) +// NewHandler creates an sdk.Handler for all the slashing type messages func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { ctx = ctx.WithEventManager(sdk.NewEventManager()) @@ -25,45 +26,12 @@ func NewHandler(k Keeper) sdk.Handler { // Validators must submit a transaction to unjail itself after // having been jailed (and thus unbonded) for downtime func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { - validator := k.sk.Validator(ctx, msg.ValidatorAddr) - if validator == nil { - return ErrNoValidatorForAddress(k.codespace).Result() + + err := k.Unjail(ctx, msg.ValidatorAddr) + if err != nil { + return err.Result() } - // cannot be unjailed if no self-delegation exists - selfDel := k.sk.Delegation(ctx, sdk.AccAddress(msg.ValidatorAddr), msg.ValidatorAddr) - if selfDel == nil { - return ErrMissingSelfDelegation(k.codespace).Result() - } - - if validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { - return ErrSelfDelegationTooLowToUnjail(k.codespace).Result() - } - - // cannot be unjailed if not jailed - if !validator.IsJailed() { - return ErrValidatorNotJailed(k.codespace).Result() - } - - consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address()) - - info, found := k.getValidatorSigningInfo(ctx, consAddr) - if !found { - return ErrNoValidatorForAddress(k.codespace).Result() - } - - // cannot be unjailed if tombstoned - if info.Tombstoned { - return ErrValidatorJailed(k.codespace).Result() - } - - // cannot be unjailed until out of jail - if ctx.BlockHeader().Time.Before(info.JailedUntil) { - return ErrValidatorJailed(k.codespace).Result() - } - - k.sk.Unjail(ctx, consAddr) - ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 854a1d140..077ea009f 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -9,23 +9,25 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/internal/keeper" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" "github.com/cosmos/cosmos-sdk/x/staking" ) func TestCannotUnjailUnlessJailed(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + ctx, ck, sk, _, keeper := slashingkeeper.CreateTestInput(t, DefaultParams()) slh := NewHandler(keeper) amt := sdk.TokensFromConsensusPower(100) - addr, val := addrs[0], pks[0] - msg := NewTestMsgCreateValidator(addr, val, amt) + addr, val := slashingkeeper.Addrs[0], slashingkeeper.Pks[0] + msg := slashingkeeper.NewTestMsgCreateValidator(addr, val, amt) got := staking.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK(), "%v", got) staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), - sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))}, + sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, slashingkeeper.InitTokens.Sub(amt))}, ) require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) @@ -38,11 +40,11 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { func TestCannotUnjailUnlessMeetMinSelfDelegation(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + ctx, ck, sk, _, keeper := slashingkeeper.CreateTestInput(t, DefaultParams()) slh := NewHandler(keeper) amtInt := int64(100) - addr, val, amt := addrs[0], pks[0], sdk.TokensFromConsensusPower(amtInt) - msg := NewTestMsgCreateValidator(addr, val, amt) + addr, val, amt := slashingkeeper.Addrs[0], slashingkeeper.Pks[0], sdk.TokensFromConsensusPower(amtInt) + msg := slashingkeeper.NewTestMsgCreateValidator(addr, val, amt) msg.MinSelfDelegation = amt got := staking.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK()) @@ -50,7 +52,7 @@ func TestCannotUnjailUnlessMeetMinSelfDelegation(t *testing.T) { require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), - sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))}, + sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, slashingkeeper.InitTokens.Sub(amt))}, ) unbondAmt := sdk.NewCoin(sk.GetParams(ctx).BondDenom, sdk.OneInt()) @@ -67,7 +69,7 @@ func TestCannotUnjailUnlessMeetMinSelfDelegation(t *testing.T) { } func TestJailedValidatorDelegations(t *testing.T) { - ctx, _, stakingKeeper, _, slashingKeeper := createTestInput(t, DefaultParams()) + ctx, _, stakingKeeper, _, slashingKeeper := slashingkeeper.CreateTestInput(t, DefaultParams()) stakingParams := stakingKeeper.GetParams(ctx) stakingParams.UnbondingTime = 0 @@ -75,10 +77,10 @@ func TestJailedValidatorDelegations(t *testing.T) { // create a validator bondAmount := sdk.TokensFromConsensusPower(10) - valPubKey := pks[0] - valAddr, consAddr := addrs[1], sdk.ConsAddress(addrs[0]) + valPubKey := slashingkeeper.Pks[0] + valAddr, consAddr := slashingkeeper.Addrs[1], sdk.ConsAddress(slashingkeeper.Addrs[0]) - msgCreateVal := NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + msgCreateVal := slashingkeeper.NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) got := staking.NewHandler(stakingKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) @@ -90,8 +92,8 @@ func TestJailedValidatorDelegations(t *testing.T) { slashingKeeper.SetValidatorSigningInfo(ctx, consAddr, newInfo) // delegate tokens to the validator - delAddr := sdk.AccAddress(addrs[2]) - msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + delAddr := sdk.AccAddress(slashingkeeper.Addrs[2]) + msgDelegate := slashingkeeper.NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = staking.NewHandler(stakingKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -115,7 +117,7 @@ func TestJailedValidatorDelegations(t *testing.T) { require.False(t, got.IsOK(), "expected jailed validator to not be able to unjail, got: %v", got) // self-delegate to validator - msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + msgSelfDelegate := slashingkeeper.NewTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) got = staking.NewHandler(stakingKeeper)(ctx, msgSelfDelegate) require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) @@ -132,3 +134,154 @@ func TestInvalidMsg(t *testing.T) { require.False(t, res.IsOK()) require.True(t, strings.Contains(res.Log, "unrecognized slashing message type")) } + +// Test a validator through uptime, downtime, revocation, +// unrevocation, starting height reset, and revocation again +func TestHandleAbsentValidator(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := slashingkeeper.CreateTestInput(t, slashingkeeper.TestParams()) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + addr, val := slashingkeeper.Addrs[0], slashingkeeper.Pks[0] + sh := staking.NewHandler(sk) + slh := NewHandler(keeper) + got := sh(ctx, slashingkeeper.NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(addr)), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, slashingkeeper.InitTokens.Sub(amt))), + ) + require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) + + // will exist since the validator has been bonded + info, found := keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.IndexOffset) + require.Equal(t, int64(0), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + height := int64(0) + + // 1000 first blocks OK + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, true) + } + info, found = keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.MissedBlocksCounter) + + // 500 blocks missed + for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + info, found = keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) + + // validator should be bonded still + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + bondPool := sk.GetBondedPool(ctx) + require.True(sdk.IntEq(t, amt, bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)))) + + // 501st block missed + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + info, found = keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + // counter now reset to zero + require.Equal(t, int64(0), info.MissedBlocksCounter) + + // end block + staking.EndBlocker(ctx, sk) + + // validator should have been jailed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) + + slashAmt := amt.ToDec().Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() + + // validator should have been slashed + require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) + + // 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator) + height++ + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + info, found = keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(1), info.MissedBlocksCounter) + + // end block + staking.EndBlocker(ctx, sk) + + // validator should not have been slashed any more, since it was already jailed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) + + // unrevocation should fail prior to jail expiration + got = slh(ctx, types.NewMsgUnjail(addr)) + require.False(t, got.IsOK()) + + // unrevocation should succeed after jail expiration + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeJailDuration(ctx))}) + got = slh(ctx, types.NewMsgUnjail(addr)) + require.True(t, got.IsOK()) + + // end block + staking.EndBlocker(ctx, sk) + + // validator should be rebonded now + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // validator should have been slashed + bondPool = sk.GetBondedPool(ctx) + require.Equal(t, amt.Int64()-slashAmt, bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)).Int64()) + + // Validator start height should not have been changed + info, found = keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 + require.Equal(t, int64(1), info.MissedBlocksCounter) + + // validator should not be immediately jailed again + height++ + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // 500 signed blocks + nextHeight := height + keeper.MinSignedPerWindow(ctx) + 1 + for ; height < nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + + // end block + staking.EndBlocker(ctx, sk) + + // validator should be jailed again after 500 unsigned blocks + nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1 + for ; height <= nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + + // end block + staking.EndBlocker(ctx, sk) + + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) +} diff --git a/x/slashing/hooks.go b/x/slashing/internal/keeper/hooks.go similarity index 93% rename from x/slashing/hooks.go rename to x/slashing/internal/keeper/hooks.go index 7535ab4ac..a8ef6e093 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/internal/keeper/hooks.go @@ -1,5 +1,5 @@ // nolint -package slashing +package keeper import ( "time" @@ -7,12 +7,12 @@ import ( "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) func (k Keeper) AfterValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sdk.ValAddress) { // Update the signing info start height or create a new signing info - _, found := k.getValidatorSigningInfo(ctx, address) + _, found := k.GetValidatorSigningInfo(ctx, address) if !found { signingInfo := types.NewValidatorSigningInfo( address, @@ -29,7 +29,7 @@ func (k Keeper) AfterValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ // When a validator is created, add the address-pubkey relation. func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { validator := k.sk.Validator(ctx, valAddr) - k.addPubkey(ctx, validator.GetConsPubKey()) + k.AddPubkey(ctx, validator.GetConsPubKey()) } // When a validator is removed, delete the address-pubkey relation. diff --git a/x/slashing/keeper.go b/x/slashing/internal/keeper/infractions.go similarity index 76% rename from x/slashing/keeper.go rename to x/slashing/internal/keeper/infractions.go index 52b186857..9dafb4080 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/internal/keeper/infractions.go @@ -1,47 +1,16 @@ -package slashing +package keeper import ( "fmt" "time" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/libs/log" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) -// Keeper of the slashing store -type Keeper struct { - storeKey sdk.StoreKey - cdc *codec.Codec - sk types.StakingKeeper - paramspace params.Subspace - - // codespace - codespace sdk.CodespaceType -} - -// NewKeeper creates a slashing keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.StakingKeeper, paramspace params.Subspace, codespace sdk.CodespaceType) Keeper { - keeper := Keeper{ - storeKey: key, - cdc: cdc, - sk: sk, - paramspace: paramspace.WithKeyTable(ParamKeyTable()), - codespace: codespace, - } - return keeper -} - -// Logger returns a module-specific logger. -func (k Keeper) Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) -} - -// handle a validator signing two blocks at the same height +// HandleDoubleSign handles a validator signing two blocks at the same height. // power: power of the double-signing validator at the height of infraction func (k Keeper) HandleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) { logger := k.Logger(ctx) @@ -52,7 +21,7 @@ func (k Keeper) HandleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // fetch the validator public key consAddr := sdk.ConsAddress(addr) - pubkey, err := k.getPubkey(ctx, addr) + pubkey, err := k.GetPubkey(ctx, addr) if err != nil { // Ignore evidence that cannot be handled. // NOTE: @@ -83,7 +52,7 @@ func (k Keeper) HandleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio } // fetch the validator signing info - signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) + signInfo, found := k.GetValidatorSigningInfo(ctx, consAddr) if !found { panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } @@ -143,19 +112,20 @@ func (k Keeper) HandleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } -// handle a validator signature, must be called once per validator per block -// TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too +// HandleValidatorSignature handles a validator signature, must be called once per validator per block. func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { logger := k.Logger(ctx) height := ctx.BlockHeight() + + // fetch the validator public key consAddr := sdk.ConsAddress(addr) - pubkey, err := k.getPubkey(ctx, addr) + pubkey, err := k.GetPubkey(ctx, addr) if err != nil { panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr)) } // fetch signing info - signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) + signInfo, found := k.GetValidatorSigningInfo(ctx, consAddr) if !found { panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } @@ -168,16 +138,16 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Update signed block bit array & counter // This counter just tracks the sum of the bit array // That way we avoid needing to read/write the whole array each time - previous := k.getValidatorMissedBlockBitArray(ctx, consAddr, index) + previous := k.GetValidatorMissedBlockBitArray(ctx, consAddr, index) missed := !signed switch { case !previous && missed: // Array value has changed from not missed to missed, increment counter - k.setValidatorMissedBlockBitArray(ctx, consAddr, index, true) + k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, true) signInfo.MissedBlocksCounter++ case previous && !missed: // Array value has changed from missed to not missed, decrement counter - k.setValidatorMissedBlockBitArray(ctx, consAddr, index, false) + k.SetValidatorMissedBlockBitArray(ctx, consAddr, index, false) signInfo.MissedBlocksCounter-- default: // Array value at this index has not changed, no need to update counter @@ -245,29 +215,3 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Set the updated signing info k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } - -func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { - addr := pubkey.Address() - k.setAddrPubkeyRelation(ctx, addr, pubkey) -} - -func (k Keeper) getPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKey, error) { - store := ctx.KVStore(k.storeKey) - var pubkey crypto.PubKey - err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(types.GetAddrPubkeyRelationKey(address)), &pubkey) - if err != nil { - return nil, fmt.Errorf("address %s not found", sdk.ConsAddress(address)) - } - return pubkey, nil -} - -func (k Keeper) setAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address, pubkey crypto.PubKey) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(pubkey) - store.Set(types.GetAddrPubkeyRelationKey(addr), bz) -} - -func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.GetAddrPubkeyRelationKey(addr)) -} diff --git a/x/slashing/internal/keeper/keeper.go b/x/slashing/internal/keeper/keeper.go new file mode 100644 index 000000000..4b4d28900 --- /dev/null +++ b/x/slashing/internal/keeper/keeper.go @@ -0,0 +1,66 @@ +package keeper + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" +) + +// Keeper of the slashing store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + sk types.StakingKeeper + paramspace types.ParamSubspace + codespace sdk.CodespaceType +} + +// NewKeeper creates a slashing keeper +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.StakingKeeper, paramspace types.ParamSubspace, codespace sdk.CodespaceType) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + sk: sk, + paramspace: paramspace.WithKeyTable(types.ParamKeyTable()), + codespace: codespace, + } + return keeper +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +// AddPubkey sets a address-pubkey relation +func (k Keeper) AddPubkey(ctx sdk.Context, pubkey crypto.PubKey) { + addr := pubkey.Address() + k.setAddrPubkeyRelation(ctx, addr, pubkey) +} + +// GetPubkey returns the pubkey from the adddress-pubkey relation +func (k Keeper) GetPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKey, error) { + store := ctx.KVStore(k.storeKey) + var pubkey crypto.PubKey + err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(types.GetAddrPubkeyRelationKey(address)), &pubkey) + if err != nil { + return nil, fmt.Errorf("address %s not found", sdk.ConsAddress(address)) + } + return pubkey, nil +} + +func (k Keeper) setAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address, pubkey crypto.PubKey) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(pubkey) + store.Set(types.GetAddrPubkeyRelationKey(addr), bz) +} + +func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.GetAddrPubkeyRelationKey(addr)) +} diff --git a/x/slashing/keeper_test.go b/x/slashing/internal/keeper/keeper_test.go similarity index 56% rename from x/slashing/keeper_test.go rename to x/slashing/internal/keeper/keeper_test.go index 523cc6485..36b67c7bb 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/internal/keeper/keeper_test.go @@ -1,4 +1,4 @@ -package slashing +package keeper import ( "testing" @@ -8,19 +8,10 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" "github.com/cosmos/cosmos-sdk/x/staking" ) -// Have to change these parameters for tests -// lest the tests take forever -func keeperTestParams() types.Params { - params := types.DefaultParams() - params.SignedBlocksWindow = 1000 - params.DowntimeJailDuration = 60 * 60 - return params -} - // ______________________________________________________________ // Test that a validator is slashed correctly @@ -28,18 +19,18 @@ func keeperTestParams() types.Params { func TestHandleDoubleSign(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + ctx, ck, sk, _, keeper := CreateTestInput(t, TestParams()) // validator added pre-genesis ctx = ctx.WithBlockHeight(-1) power := int64(100) amt := sdk.TokensFromConsensusPower(power) - operatorAddr, val := addrs[0], pks[0] + operatorAddr, val := Addrs[0], Pks[0] got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), - sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), ) require.Equal(t, amt, sk.Validator(ctx, operatorAddr).GetBondedTokens()) @@ -68,9 +59,7 @@ func TestHandleDoubleSign(t *testing.T) { ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(sk.GetParams(ctx).UnbondingTime)}) // Still shouldn't be able to unjail - msgUnjail := types.NewMsgUnjail(operatorAddr) - res := handleMsgUnjail(ctx, msgUnjail, keeper) - require.False(t, res.IsOK()) + require.Error(t, keeper.Unjail(ctx, operatorAddr)) // Should be able to unbond now del, _ := sk.GetDelegation(ctx, sdk.AccAddress(operatorAddr), operatorAddr) @@ -78,7 +67,7 @@ func TestHandleDoubleSign(t *testing.T) { totalBond := validator.TokensFromShares(del.GetShares()).TruncateInt() msgUnbond := staking.NewMsgUndelegate(sdk.AccAddress(operatorAddr), operatorAddr, sdk.NewCoin(sk.GetParams(ctx).BondDenom, totalBond)) - res = staking.NewHandler(sk)(ctx, msgUnbond) + res := staking.NewHandler(sk)(ctx, msgUnbond) require.True(t, res.IsOK()) } @@ -89,18 +78,18 @@ func TestHandleDoubleSign(t *testing.T) { func TestPastMaxEvidenceAge(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + ctx, ck, sk, _, keeper := CreateTestInput(t, TestParams()) // validator added pre-genesis ctx = ctx.WithBlockHeight(-1) power := int64(100) amt := sdk.TokensFromConsensusPower(power) - operatorAddr, val := addrs[0], pks[0] + operatorAddr, val := Addrs[0], Pks[0] got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) staking.EndBlocker(ctx, sk) require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), - sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), ) require.Equal(t, amt, sk.Validator(ctx, operatorAddr).GetBondedTokens()) @@ -121,164 +110,13 @@ func TestPastMaxEvidenceAge(t *testing.T) { require.Equal(t, oldPower, sk.Validator(ctx, operatorAddr).GetConsensusPower()) } -// Test a validator through uptime, downtime, revocation, -// unrevocation, starting height reset, and revocation again -func TestHandleAbsentValidator(t *testing.T) { - - // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) - power := int64(100) - amt := sdk.TokensFromConsensusPower(power) - addr, val := addrs[0], pks[0] - sh := staking.NewHandler(sk) - slh := NewHandler(keeper) - got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) - require.True(t, got.IsOK()) - staking.EndBlocker(ctx, sk) - - require.Equal( - t, ck.GetCoins(ctx, sdk.AccAddress(addr)), - sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), - ) - require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) - - // will exist since the validator has been bonded - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, int64(0), info.IndexOffset) - require.Equal(t, int64(0), info.MissedBlocksCounter) - require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) - height := int64(0) - - // 1000 first blocks OK - for ; height < keeper.SignedBlocksWindow(ctx); height++ { - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, true) - } - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, int64(0), info.MissedBlocksCounter) - - // 500 blocks missed - for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, false) - } - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) - - // validator should be bonded still - validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, sdk.Bonded, validator.GetStatus()) - bondPool := sk.GetBondedPool(ctx) - require.True(sdk.IntEq(t, amt, bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)))) - - // 501st block missed - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, false) - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(0), info.StartHeight) - // counter now reset to zero - require.Equal(t, int64(0), info.MissedBlocksCounter) - - // end block - staking.EndBlocker(ctx, sk) - - // validator should have been jailed - validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, sdk.Unbonding, validator.GetStatus()) - - slashAmt := amt.ToDec().Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() - - // validator should have been slashed - require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) - - // 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator) - height++ - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, false) - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(0), info.StartHeight) - require.Equal(t, int64(1), info.MissedBlocksCounter) - - // end block - staking.EndBlocker(ctx, sk) - - // validator should not have been slashed any more, since it was already jailed - validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) - - // unrevocation should fail prior to jail expiration - got = slh(ctx, NewMsgUnjail(addr)) - require.False(t, got.IsOK()) - - // unrevocation should succeed after jail expiration - ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeJailDuration(ctx))}) - got = slh(ctx, NewMsgUnjail(addr)) - require.True(t, got.IsOK()) - - // end block - staking.EndBlocker(ctx, sk) - - // validator should be rebonded now - validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, sdk.Bonded, validator.GetStatus()) - - // validator should have been slashed - bondPool = sk.GetBondedPool(ctx) - require.Equal(t, amt.Int64()-slashAmt, bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)).Int64()) - - // Validator start height should not have been changed - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(0), info.StartHeight) - // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 - require.Equal(t, int64(1), info.MissedBlocksCounter) - - // validator should not be immediately jailed again - height++ - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, false) - validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, sdk.Bonded, validator.GetStatus()) - - // 500 signed blocks - nextHeight := height + keeper.MinSignedPerWindow(ctx) + 1 - for ; height < nextHeight; height++ { - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, false) - } - - // end block - staking.EndBlocker(ctx, sk) - - // validator should be jailed again after 500 unsigned blocks - nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1 - for ; height <= nextHeight; height++ { - ctx = ctx.WithBlockHeight(height) - keeper.HandleValidatorSignature(ctx, val.Address(), power, false) - } - - // end block - staking.EndBlocker(ctx, sk) - - validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) - require.Equal(t, sdk.Unbonding, validator.GetStatus()) -} - // Test a new validator entering the validator set // Ensure that SigningInfo.StartHeight is set correctly // and that they are not immediately jailed func TestHandleNewValidator(t *testing.T) { // initial setup - ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) - addr, val := addrs[0], pks[0] + ctx, ck, sk, _, keeper := CreateTestInput(t, TestParams()) + addr, val := Addrs[0], Pks[0] amt := sdk.TokensFromConsensusPower(100) sh := staking.NewHandler(sk) @@ -292,7 +130,7 @@ func TestHandleNewValidator(t *testing.T) { require.Equal( t, ck.GetCoins(ctx, sdk.AccAddress(addr)), - sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), ) require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) @@ -301,7 +139,7 @@ func TestHandleNewValidator(t *testing.T) { ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) keeper.HandleValidatorSignature(ctx, val.Address(), 100, false) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + info, found := keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, keeper.SignedBlocksWindow(ctx)+1, info.StartHeight) require.Equal(t, int64(2), info.IndexOffset) @@ -321,10 +159,10 @@ func TestHandleNewValidator(t *testing.T) { func TestHandleAlreadyJailed(t *testing.T) { // initial setup - ctx, _, sk, _, keeper := createTestInput(t, DefaultParams()) + ctx, _, sk, _, keeper := CreateTestInput(t, types.DefaultParams()) power := int64(100) amt := sdk.TokensFromConsensusPower(power) - addr, val := addrs[0], pks[0] + addr, val := Addrs[0], Pks[0] sh := staking.NewHandler(sk) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) @@ -370,14 +208,14 @@ func TestHandleAlreadyJailed(t *testing.T) { func TestValidatorDippingInAndOut(t *testing.T) { // initial setup - // keeperTestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500 - ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams()) + // TestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500 + ctx, _, sk, _, keeper := CreateTestInput(t, TestParams()) params := sk.GetParams(ctx) params.MaxValidators = 1 sk.SetParams(ctx, params) power := int64(100) amt := sdk.TokensFromConsensusPower(power) - addr, val := addrs[0], pks[0] + addr, val := Addrs[0], Pks[0] consAddr := sdk.ConsAddress(addr) sh := staking.NewHandler(sk) got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) @@ -393,7 +231,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { // kick first validator out of validator set newAmt := sdk.TokensFromConsensusPower(101) - got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], newAmt)) + got = sh(ctx, NewTestMsgCreateValidator(Addrs[1], Pks[1], newAmt)) require.True(t, got.IsOK()) validatorUpdates := staking.EndBlocker(ctx, sk) require.Equal(t, 2, len(validatorUpdates)) @@ -406,7 +244,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { // validator added back in delTokens := sdk.TokensFromConsensusPower(50) - got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], delTokens)) + got = sh(ctx, NewTestMsgDelegate(sdk.AccAddress(Addrs[2]), Addrs[0], delTokens)) require.True(t, got.IsOK()) validatorUpdates = staking.EndBlocker(ctx, sk) require.Equal(t, 2, len(validatorUpdates)) @@ -435,13 +273,13 @@ func TestValidatorDippingInAndOut(t *testing.T) { require.Equal(t, sdk.Unbonding, validator.Status) // check all the signing information - signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr) + signInfo, found := keeper.GetValidatorSigningInfo(ctx, consAddr) require.True(t, found) require.Equal(t, int64(0), signInfo.MissedBlocksCounter) require.Equal(t, int64(0), signInfo.IndexOffset) // array should be cleared for offset := int64(0); offset < keeper.SignedBlocksWindow(ctx); offset++ { - missed := keeper.getValidatorMissedBlockBitArray(ctx, consAddr, offset) + missed := keeper.GetValidatorMissedBlockBitArray(ctx, consAddr, offset) require.False(t, missed) } diff --git a/x/slashing/params.go b/x/slashing/internal/keeper/params.go similarity index 74% rename from x/slashing/params.go rename to x/slashing/internal/keeper/params.go index dfe676a88..124588dc7 100644 --- a/x/slashing/params.go +++ b/x/slashing/internal/keeper/params.go @@ -1,10 +1,10 @@ -package slashing +package keeper import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) // MaxEvidenceAge - max age for evidence @@ -19,7 +19,7 @@ func (k Keeper) SignedBlocksWindow(ctx sdk.Context) (res int64) { return } -// Downtime slashing threshold +// MinSignedPerWindow - minimum blocks signed per window func (k Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { var minSignedPerWindow sdk.Dec k.paramspace.Get(ctx, types.KeyMinSignedPerWindow, &minSignedPerWindow) @@ -30,19 +30,19 @@ func (k Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { return minSignedPerWindow.MulInt64(signedBlocksWindow).RoundInt64() } -// Downtime unbond duration +// DowntimeJailDuration - Downtime unbond duration func (k Keeper) DowntimeJailDuration(ctx sdk.Context) (res time.Duration) { k.paramspace.Get(ctx, types.KeyDowntimeJailDuration, &res) return } -// SlashFractionDoubleSign +// SlashFractionDoubleSign - fraction of power slashed in case of double sign func (k Keeper) SlashFractionDoubleSign(ctx sdk.Context) (res sdk.Dec) { k.paramspace.Get(ctx, types.KeySlashFractionDoubleSign, &res) return } -// SlashFractionDowntime +// SlashFractionDowntime - fraction of power slashed for downtime func (k Keeper) SlashFractionDowntime(ctx sdk.Context) (res sdk.Dec) { k.paramspace.Get(ctx, types.KeySlashFractionDowntime, &res) return @@ -53,3 +53,8 @@ func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { k.paramspace.GetParamSet(ctx, ¶ms) return params } + +// SetParams sets the slashing parameters to the param space. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramspace.SetParamSet(ctx, ¶ms) +} diff --git a/x/slashing/querier.go b/x/slashing/internal/keeper/querier.go similarity index 68% rename from x/slashing/querier.go rename to x/slashing/internal/keeper/querier.go index 32be853f9..4633d8ba5 100644 --- a/x/slashing/querier.go +++ b/x/slashing/internal/keeper/querier.go @@ -1,4 +1,4 @@ -package slashing +package keeper import ( "fmt" @@ -8,17 +8,18 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) // NewQuerier creates a new querier for slashing clients. func NewQuerier(k Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { switch path[0] { - case QueryParameters: + case types.QueryParameters: return queryParams(ctx, k) - case QuerySigningInfo: + case types.QuerySigningInfo: return querySigningInfo(ctx, req, k) - case QuerySigningInfos: + case types.QuerySigningInfos: return querySigningInfos(ctx, req, k) default: return nil, sdk.ErrUnknownRequest("unknown staking query endpoint") @@ -29,7 +30,7 @@ func NewQuerier(k Keeper) sdk.Querier { func queryParams(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { params := k.GetParams(ctx) - res, err := codec.MarshalJSONIndent(ModuleCdc, params) + res, err := codec.MarshalJSONIndent(types.ModuleCdc, params) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to marshal JSON", err.Error())) } @@ -38,19 +39,19 @@ func queryParams(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { } func querySigningInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params QuerySigningInfoParams + var params types.QuerySigningInfoParams - err := ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } - signingInfo, found := k.getValidatorSigningInfo(ctx, params.ConsAddress) + signingInfo, found := k.GetValidatorSigningInfo(ctx, params.ConsAddress) if !found { - return nil, ErrNoSigningInfoFound(DefaultCodespace, params.ConsAddress) + return nil, types.ErrNoSigningInfoFound(types.DefaultCodespace, params.ConsAddress) } - res, err := codec.MarshalJSONIndent(ModuleCdc, signingInfo) + res, err := codec.MarshalJSONIndent(types.ModuleCdc, signingInfo) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error())) } @@ -59,28 +60,28 @@ func querySigningInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, } func querySigningInfos(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params QuerySigningInfosParams + var params types.QuerySigningInfosParams - err := ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } - var signingInfos []ValidatorSigningInfo + var signingInfos []types.ValidatorSigningInfo - k.IterateValidatorSigningInfos(ctx, func(consAddr sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { + k.IterateValidatorSigningInfos(ctx, func(consAddr sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) { signingInfos = append(signingInfos, info) return false }) start, end := client.Paginate(len(signingInfos), params.Page, params.Limit, int(k.sk.MaxValidators(ctx))) if start < 0 || end < 0 { - signingInfos = []ValidatorSigningInfo{} + signingInfos = []types.ValidatorSigningInfo{} } else { signingInfos = signingInfos[start:end] } - res, err := codec.MarshalJSONIndent(ModuleCdc, signingInfos) + res, err := codec.MarshalJSONIndent(types.ModuleCdc, signingInfos) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error())) } diff --git a/x/slashing/querier_test.go b/x/slashing/internal/keeper/querier_test.go similarity index 74% rename from x/slashing/querier_test.go rename to x/slashing/internal/keeper/querier_test.go index a4941e0a9..ea4095dc6 100644 --- a/x/slashing/querier_test.go +++ b/x/slashing/internal/keeper/querier_test.go @@ -1,4 +1,4 @@ -package slashing +package keeper import ( "testing" @@ -7,10 +7,11 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) func TestNewQuerier(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, keeperTestParams()) + ctx, _, _, _, keeper := CreateTestInput(t, TestParams()) querier := NewQuerier(keeper) query := abci.RequestQuery{ @@ -24,9 +25,9 @@ func TestNewQuerier(t *testing.T) { func TestQueryParams(t *testing.T) { cdc := codec.New() - ctx, _, _, _, keeper := createTestInput(t, keeperTestParams()) + ctx, _, _, _, keeper := CreateTestInput(t, TestParams()) - var params Params + var params types.Params res, errRes := queryParams(ctx, keeper) require.NoError(t, errRes) diff --git a/x/slashing/signing_info.go b/x/slashing/internal/keeper/signing_info.go similarity index 72% rename from x/slashing/signing_info.go rename to x/slashing/internal/keeper/signing_info.go index f9cce43ac..ad62975f7 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/internal/keeper/signing_info.go @@ -1,12 +1,13 @@ -package slashing +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) -// Stored by *validator* address (not operator address) -func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info types.ValidatorSigningInfo, found bool) { +// GetValidatorSigningInfo retruns the ValidatorSigningInfo for a specific validator +// ConsAddress +func (k Keeper) GetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info types.ValidatorSigningInfo, found bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetValidatorSigningInfoKey(address)) if bz == nil { @@ -18,7 +19,15 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress return } -// Stored by *validator* address (not operator address) +// 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.MustMarshalBinaryLengthPrefixed(info) + store.Set(types.GetValidatorSigningInfoKey(address), bz) +} + + +// IterateValidatorSigningInfos iterates over the stored ValidatorSigningInfo func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, handler func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool)) { @@ -35,15 +44,8 @@ func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, } } -// Stored by *validator* address (not operator address) -func (k Keeper) SetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info types.ValidatorSigningInfo) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(info) - store.Set(types.GetValidatorSigningInfoKey(address), bz) -} - -// Stored by *validator* address (not operator address) -func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (missed bool) { +// GetValidatorMissedBlockBitArray gets the bit for the missed blocks array +func (k Keeper) GetValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (missed bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetValidatorMissedBlockBitArrayKey(address, index)) if bz == nil { @@ -55,7 +57,8 @@ func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.Con return } -// Stored by *validator* address (not operator address) +// IterateValidatorMissedBlockBitArray iterates over the signed blocks window +// and performs a callback function func (k Keeper) IterateValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool)) { @@ -75,14 +78,15 @@ func (k Keeper) IterateValidatorMissedBlockBitArray(ctx sdk.Context, } } -// Stored by *validator* address (not operator address) -func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) { +// SetValidatorMissedBlockBitArray sets the bit that checks if the validator has +// missed a block in the current window +func (k Keeper) SetValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(missed) store.Set(types.GetValidatorMissedBlockBitArrayKey(address, index), bz) } -// Stored by *validator* address (not operator address) +// clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, types.GetValidatorMissedBlockBitArrayPrefixKey(address)) diff --git a/x/slashing/internal/keeper/signing_info_test.go b/x/slashing/internal/keeper/signing_info_test.go new file mode 100644 index 000000000..e7d302f48 --- /dev/null +++ b/x/slashing/internal/keeper/signing_info_test.go @@ -0,0 +1,41 @@ +package keeper + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" +) + +func TestGetSetValidatorSigningInfo(t *testing.T) { + ctx, _, _, _, keeper := CreateTestInput(t, types.DefaultParams()) + info, found := keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(Addrs[0])) + require.False(t, found) + newInfo := types.NewValidatorSigningInfo( + sdk.ConsAddress(Addrs[0]), + int64(4), + int64(3), + time.Unix(2, 0), + false, + int64(10), + ) + keeper.SetValidatorSigningInfo(ctx, sdk.ConsAddress(Addrs[0]), newInfo) + info, found = keeper.GetValidatorSigningInfo(ctx, sdk.ConsAddress(Addrs[0])) + require.True(t, found) + require.Equal(t, info.StartHeight, int64(4)) + require.Equal(t, info.IndexOffset, int64(3)) + require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) + require.Equal(t, info.MissedBlocksCounter, int64(10)) +} + +func TestGetSetValidatorMissedBlockBitArray(t *testing.T) { + ctx, _, _, _, keeper := CreateTestInput(t, types.DefaultParams()) + missed := keeper.GetValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(Addrs[0]), 0) + require.False(t, missed) // treat empty key as not missed + keeper.SetValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(Addrs[0]), 0, true) + missed = keeper.GetValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(Addrs[0]), 0) + require.True(t, missed) // now should be missed +} diff --git a/x/slashing/test_common.go b/x/slashing/internal/keeper/test_common.go similarity index 79% rename from x/slashing/test_common.go rename to x/slashing/internal/keeper/test_common.go index ac498e5a7..f4c409bb1 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/internal/keeper/test_common.go @@ -1,5 +1,7 @@ // nolint:deadcode unused -package slashing +// DONTCOVER +// noalias +package keeper import ( "encoding/hex" @@ -20,6 +22,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" ) @@ -27,18 +30,18 @@ import ( // TODO remove dependencies on staking (should only refer to validator set type from sdk) var ( - pks = []crypto.PubKey{ + Pks = []crypto.PubKey{ newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), } - addrs = []sdk.ValAddress{ - sdk.ValAddress(pks[0].Address()), - sdk.ValAddress(pks[1].Address()), - sdk.ValAddress(pks[2].Address()), + Addrs = []sdk.ValAddress{ + sdk.ValAddress(Pks[0].Address()), + sdk.ValAddress(Pks[1].Address()), + sdk.ValAddress(Pks[2].Address()), } - initTokens = sdk.TokensFromConsensusPower(200) - initCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens)) + InitTokens = sdk.TokensFromConsensusPower(200) + initCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, InitTokens)) ) func createTestCodec() *codec.Codec { @@ -52,11 +55,11 @@ func createTestCodec() *codec.Codec { return cdc } -func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, staking.Keeper, params.Subspace, Keeper) { +func CreateTestInput(t *testing.T, defaults types.Params) (sdk.Context, bank.Keeper, staking.Keeper, params.Subspace, Keeper) { keyAcc := sdk.NewKVStoreKey(auth.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) - keySlashing := sdk.NewKVStoreKey(StoreKey) + keySlashing := sdk.NewKVStoreKey(types.StoreKey) keySupply := sdk.NewKVStoreKey(supply.StoreKey) keyParams := sdk.NewKVStoreKey(params.StoreKey) tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) @@ -84,7 +87,7 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s } supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bk, supply.DefaultCodespace, maccPerms) - totalSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens.MulRaw(int64(len(addrs))))) + totalSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, InitTokens.MulRaw(int64(len(Addrs))))) supplyKeeper.SetSupply(ctx, supply.NewSupply(totalSupply)) sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, supplyKeeper, paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) @@ -101,17 +104,15 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s _ = staking.InitGenesis(ctx, sk, accountKeeper, supplyKeeper, genesis) - for _, addr := range addrs { + for _, addr := range Addrs { _, err = bk.AddCoins(ctx, sdk.AccAddress(addr), initCoins) } require.Nil(t, err) - paramstore := paramsKeeper.Subspace(DefaultParamspace) - keeper := NewKeeper(cdc, keySlashing, &sk, paramstore, DefaultCodespace) - sk.SetHooks(keeper.Hooks()) + paramstore := paramsKeeper.Subspace(types.DefaultParamspace) + keeper := NewKeeper(cdc, keySlashing, &sk, paramstore, types.DefaultCodespace) - require.NotPanics(t, func() { - InitGenesis(ctx, keeper, sk, GenesisState{defaults, nil, nil}) - }) + keeper.SetParams(ctx, defaults) + sk.SetHooks(keeper.Hooks()) return ctx, bk, sk, paramstore, keeper } @@ -126,9 +127,13 @@ func newPubKey(pk string) (res crypto.PubKey) { return pkEd } -func testAddr(addr string) sdk.AccAddress { - res := []byte(addr) - return res +// Have to change these parameters for tests +// lest the tests take forever +func TestParams() types.Params { + params := types.DefaultParams() + params.SignedBlocksWindow = 1000 + params.DowntimeJailDuration = 60 * 60 + return params } func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) staking.MsgCreateValidator { @@ -139,7 +144,7 @@ func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt ) } -func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) staking.MsgDelegate { +func NewTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) staking.MsgDelegate { amount := sdk.NewCoin(sdk.DefaultBondDenom, delAmount) return staking.NewMsgDelegate(delAddr, valAddr, amount) } diff --git a/x/slashing/internal/keeper/unjail.go b/x/slashing/internal/keeper/unjail.go new file mode 100644 index 000000000..38f7e3a54 --- /dev/null +++ b/x/slashing/internal/keeper/unjail.go @@ -0,0 +1,50 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" +) + +// 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) sdk.Error { + validator := k.sk.Validator(ctx, validatorAddr) + if validator == nil { + return types.ErrNoValidatorForAddress(k.codespace) + } + + // cannot be unjailed if no self-delegation exists + selfDel := k.sk.Delegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + if selfDel == nil { + return types.ErrMissingSelfDelegation(k.codespace) + } + + if validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { + return types.ErrSelfDelegationTooLowToUnjail(k.codespace) + } + + // cannot be unjailed if not jailed + if !validator.IsJailed() { + return types.ErrValidatorNotJailed(k.codespace) + } + + consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address()) + + info, found := k.GetValidatorSigningInfo(ctx, consAddr) + if !found { + return types.ErrNoValidatorForAddress(k.codespace) + } + + // cannot be unjailed if tombstoned + if info.Tombstoned { + return types.ErrValidatorJailed(k.codespace) + } + + // cannot be unjailed until out of jail + if ctx.BlockHeader().Time.Before(info.JailedUntil) { + return types.ErrValidatorJailed(k.codespace) + } + + k.sk.Unjail(ctx, consAddr) + return nil +} \ No newline at end of file diff --git a/x/slashing/types/codec.go b/x/slashing/internal/types/codec.go similarity index 78% rename from x/slashing/types/codec.go rename to x/slashing/internal/types/codec.go index 1a4d62c4e..34790d54d 100644 --- a/x/slashing/types/codec.go +++ b/x/slashing/internal/types/codec.go @@ -4,12 +4,12 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on codec codec +// RegisterCodec registers concrete types on codec func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgUnjail{}, "cosmos-sdk/MsgUnjail", nil) } -// module codec +// ModuleCdc defines the module codec var ModuleCdc *codec.Codec func init() { diff --git a/x/slashing/types/errors.go b/x/slashing/internal/types/errors.go similarity index 99% rename from x/slashing/types/errors.go rename to x/slashing/internal/types/errors.go index 2e0fa8349..b00e6e4f9 100644 --- a/x/slashing/types/errors.go +++ b/x/slashing/internal/types/errors.go @@ -7,6 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// DONTCOVER + // Local code type type CodeType = sdk.CodeType diff --git a/x/slashing/types/events.go b/x/slashing/internal/types/events.go similarity index 96% rename from x/slashing/types/events.go rename to x/slashing/internal/types/events.go index 2ba4020bd..62b6493c3 100644 --- a/x/slashing/types/events.go +++ b/x/slashing/internal/types/events.go @@ -1,7 +1,8 @@ +//noalias package types // Slashing module event types -var ( +const ( EventTypeSlash = "slash" EventTypeLiveness = "liveness" diff --git a/x/slashing/types/expected_keepers.go b/x/slashing/internal/types/expected_keepers.go similarity index 80% rename from x/slashing/types/expected_keepers.go rename to x/slashing/internal/types/expected_keepers.go index 4626e079b..c73ab095e 100644 --- a/x/slashing/types/expected_keepers.go +++ b/x/slashing/internal/types/expected_keepers.go @@ -1,8 +1,11 @@ -package types // noalias +// noalias +// DONTCOVER +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/staking/exported" ) @@ -11,6 +14,14 @@ type AccountKeeper interface { IterateAccounts(ctx sdk.Context, process func(auth.Account) (stop bool)) } +// ParamSubspace defines the expected Subspace interfacace +type ParamSubspace interface { + WithKeyTable(table params.KeyTable) params.Subspace + Get(ctx sdk.Context, key []byte, ptr interface{}) + GetParamSet(ctx sdk.Context, ps params.ParamSet) + SetParamSet(ctx sdk.Context, ps params.ParamSet) +} + // StakingKeeper expected staking keeper type StakingKeeper interface { // iterate through validators by operator address, execute func for each validator @@ -33,7 +44,7 @@ type StakingKeeper interface { MaxValidators(sdk.Context) uint16 } -// StakingHooks event hooks for staking validator object +// StakingHooks event hooks for staking validator object (noalias) type StakingHooks interface { AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator is created AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is deleted diff --git a/x/slashing/types/genesis.go b/x/slashing/internal/types/genesis.go similarity index 93% rename from x/slashing/types/genesis.go rename to x/slashing/internal/types/genesis.go index b72c0637b..649c041cb 100644 --- a/x/slashing/types/genesis.go +++ b/x/slashing/internal/types/genesis.go @@ -32,6 +32,14 @@ type MissedBlock struct { Missed bool `json:"missed" yaml:"missed"` } +// NewMissedBlock creates a new MissedBlock instance +func NewMissedBlock(index int64, missed bool) MissedBlock { + return MissedBlock{ + Index: index, + Missed: missed, + } +} + // DefaultGenesisState - default GenesisState used by Cosmos Hub func DefaultGenesisState() GenesisState { return GenesisState{ diff --git a/x/slashing/types/keys.go b/x/slashing/internal/types/keys.go similarity index 76% rename from x/slashing/types/keys.go rename to x/slashing/internal/types/keys.go index 294b9c9a1..865b0d5f1 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/internal/types/keys.go @@ -7,7 +7,7 @@ import ( ) const ( - // module name + // ModuleName is the name of the module ModuleName = "slashing" // StoreKey is the store key string for slashing @@ -20,13 +20,6 @@ const ( QuerierRoute = ModuleName ) -// Query endpoints supported by the slashing querier -const ( - QueryParameters = "parameters" - QuerySigningInfo = "signingInfo" - QuerySigningInfos = "signingInfos" -) - // Keys for slashing store // Items are stored with the following key: values // @@ -41,12 +34,12 @@ var ( AddrPubkeyRelationKey = []byte{0x03} // Prefix for address-pubkey relation ) -// stored by *Consensus* address (not operator address) +// GetValidatorSigningInfoKey - stored by *Consensus* address (not operator address) func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte { return append(ValidatorSigningInfoKey, v.Bytes()...) } -// extract the address from a validator signing info key +// GetValidatorSigningInfoAddress - extract the address from a validator signing info key func GetValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { addr := key[1:] if len(addr) != sdk.AddrLen { @@ -55,19 +48,19 @@ func GetValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { return sdk.ConsAddress(addr) } -// stored by *Consensus* address (not operator address) +// GetValidatorMissedBlockBitArrayPrefixKey - stored by *Consensus* address (not operator address) func GetValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte { return append(ValidatorMissedBlockBitArrayKey, v.Bytes()...) } -// stored by *Consensus* address (not operator address) +// GetValidatorMissedBlockBitArrayKey - stored by *Consensus* address (not operator address) func GetValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, uint64(i)) return append(GetValidatorMissedBlockBitArrayPrefixKey(v), b...) } -// get pubkey relation key used to get the pubkey from the address +// GetAddrPubkeyRelationKey gets pubkey relation key used to get the pubkey from the address func GetAddrPubkeyRelationKey(address []byte) []byte { return append(AddrPubkeyRelationKey, address...) } diff --git a/x/slashing/types/msg.go b/x/slashing/internal/types/msg.go similarity index 84% rename from x/slashing/types/msg.go rename to x/slashing/internal/types/msg.go index a167ae44b..3513d3b6c 100644 --- a/x/slashing/types/msg.go +++ b/x/slashing/internal/types/msg.go @@ -12,6 +12,7 @@ type MsgUnjail struct { ValidatorAddr sdk.ValAddress `json:"address" yaml:"address"` // address of the validator operator } +// NewMsgUnjail creates a new MsgUnjail instance func NewMsgUnjail(validatorAddr sdk.ValAddress) MsgUnjail { return MsgUnjail{ ValidatorAddr: validatorAddr, @@ -25,13 +26,13 @@ func (msg MsgUnjail) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} } -// get the bytes for the message signer to sign on +// GetSignBytes gets the bytes for the message signer to sign on func (msg MsgUnjail) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } -// quick validity check +// ValidateBasic validity check for the AnteHandler func (msg MsgUnjail) ValidateBasic() sdk.Error { if msg.ValidatorAddr.Empty() { return ErrBadValidatorAddr(DefaultCodespace) diff --git a/x/slashing/types/msg_test.go b/x/slashing/internal/types/msg_test.go similarity index 100% rename from x/slashing/types/msg_test.go rename to x/slashing/internal/types/msg_test.go diff --git a/x/slashing/types/params.go b/x/slashing/internal/types/params.go similarity index 79% rename from x/slashing/types/params.go rename to x/slashing/internal/types/params.go index 532324a28..f66241d4d 100644 --- a/x/slashing/types/params.go +++ b/x/slashing/internal/types/params.go @@ -64,6 +64,7 @@ func NewParams(maxEvidenceAge time.Duration, signedBlocksWindow int64, } } +// String implements the stringer interface for Params func (p Params) String() string { return fmt.Sprintf(`Slashing Params: MaxEvidenceAge: %s @@ -77,26 +78,22 @@ func (p Params) String() string { p.SlashFractionDowntime) } -// Implements params.ParamSet +// ParamSetPairs - Implements params.ParamSet func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ - {KeyMaxEvidenceAge, &p.MaxEvidenceAge}, - {KeySignedBlocksWindow, &p.SignedBlocksWindow}, - {KeyMinSignedPerWindow, &p.MinSignedPerWindow}, - {KeyDowntimeJailDuration, &p.DowntimeJailDuration}, - {KeySlashFractionDoubleSign, &p.SlashFractionDoubleSign}, - {KeySlashFractionDowntime, &p.SlashFractionDowntime}, + params.NewParamSetPair(KeyMaxEvidenceAge, &p.MaxEvidenceAge), + params.NewParamSetPair(KeySignedBlocksWindow, &p.SignedBlocksWindow), + params.NewParamSetPair(KeyMinSignedPerWindow, &p.MinSignedPerWindow), + params.NewParamSetPair(KeyDowntimeJailDuration, &p.DowntimeJailDuration), + params.NewParamSetPair(KeySlashFractionDoubleSign, &p.SlashFractionDoubleSign), + params.NewParamSetPair(KeySlashFractionDowntime, &p.SlashFractionDowntime), } } -// Default parameters for this module +// DefaultParams defines the parameters for this module func DefaultParams() Params { - return Params{ - MaxEvidenceAge: DefaultMaxEvidenceAge, - SignedBlocksWindow: DefaultSignedBlocksWindow, - MinSignedPerWindow: DefaultMinSignedPerWindow, - DowntimeJailDuration: DefaultDowntimeJailDuration, - SlashFractionDoubleSign: DefaultSlashFractionDoubleSign, - SlashFractionDowntime: DefaultSlashFractionDowntime, - } + return NewParams( + DefaultMaxEvidenceAge, DefaultSignedBlocksWindow, DefaultMinSignedPerWindow, + DefaultDowntimeJailDuration, DefaultSlashFractionDoubleSign, DefaultSlashFractionDowntime, + ) } diff --git a/x/slashing/types/querier.go b/x/slashing/internal/types/querier.go similarity index 66% rename from x/slashing/types/querier.go rename to x/slashing/internal/types/querier.go index d8850b0b2..b149886c3 100644 --- a/x/slashing/types/querier.go +++ b/x/slashing/internal/types/querier.go @@ -4,12 +4,22 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// DONTCOVER + +// Query endpoints supported by the slashing querier +const ( + QueryParameters = "parameters" + QuerySigningInfo = "signingInfo" + QuerySigningInfos = "signingInfos" +) + // QuerySigningInfoParams defines the params for the following queries: // - 'custom/slashing/signingInfo' type QuerySigningInfoParams struct { ConsAddress sdk.ConsAddress } +// NewQuerySigningInfoParams creates a new QuerySigningInfoParams instance func NewQuerySigningInfoParams(consAddr sdk.ConsAddress) QuerySigningInfoParams { return QuerySigningInfoParams{consAddr} } @@ -20,6 +30,7 @@ type QuerySigningInfosParams struct { Page, Limit int } +// NewQuerySigningInfosParams creates a new QuerySigningInfosParams instance func NewQuerySigningInfosParams(page, limit int) QuerySigningInfosParams { return QuerySigningInfosParams{page, limit} } diff --git a/x/slashing/types/signing_info.go b/x/slashing/internal/types/signing_info.go similarity index 89% rename from x/slashing/types/signing_info.go rename to x/slashing/internal/types/signing_info.go index 804db7b97..431658c22 100644 --- a/x/slashing/types/signing_info.go +++ b/x/slashing/internal/types/signing_info.go @@ -7,7 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Signing info for a validator +// ValidatorSigningInfo defines the signing info for a validator type ValidatorSigningInfo struct { Address sdk.ConsAddress `json:"address" yaml:"address"` // validator consensus address StartHeight int64 `json:"start_height" yaml:"start_height"` // height at which validator was first a candidate OR was unjailed @@ -17,7 +17,7 @@ type ValidatorSigningInfo struct { MissedBlocksCounter int64 `json:"missed_blocks_counter" yaml:"missed_blocks_counter"` // missed blocks counter (to avoid scanning the array every time) } -// Construct a new `ValidatorSigningInfo` struct +// NewValidatorSigningInfo creates a new ValidatorSigningInfo instance func NewValidatorSigningInfo( condAddr sdk.ConsAddress, startHeight, indexOffset int64, jailedUntil time.Time, tombstoned bool, missedBlocksCounter int64, @@ -33,7 +33,7 @@ func NewValidatorSigningInfo( } } -// Return human readable signing info +// String implements the stringer interface for ValidatorSigningInfo func (i ValidatorSigningInfo) String() string { return fmt.Sprintf(`Validator Signing Info: Address: %s diff --git a/x/slashing/module.go b/x/slashing/module.go index 3538f96d6..e3ee17fc2 100644 --- a/x/slashing/module.go +++ b/x/slashing/module.go @@ -14,7 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" - "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/slashing/internal/types" ) var ( diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go deleted file mode 100644 index d6803af8d..000000000 --- a/x/slashing/signing_info_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package slashing - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestGetSetValidatorSigningInfo(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) - require.False(t, found) - newInfo := NewValidatorSigningInfo( - sdk.ConsAddress(addrs[0]), - int64(4), - int64(3), - time.Unix(2, 0), - false, - int64(10), - ) - keeper.SetValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) - require.True(t, found) - require.Equal(t, info.StartHeight, int64(4)) - require.Equal(t, info.IndexOffset, int64(3)) - require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) - require.Equal(t, info.MissedBlocksCounter, int64(10)) -} - -func TestGetSetValidatorMissedBlockBitArray(t *testing.T) { - ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) - require.False(t, missed) // treat empty key as not missed - keeper.setValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) - missed = keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) - require.True(t, missed) // now should be missed -}