Skip to content

Commit

Permalink
feat: Add AfterValidatorDowntime hook in slashing module (#10938)
Browse files Browse the repository at this point in the history
Create a slashing hook that allows external modules to register an execution when a validator has downtime.

Implementation details:

* Call hook in HandleValidatorSignature (x/slashing/keeper/infractions.go) which updates validator SignInfo data

* Defer hook execution in case it also wants to update validator SignInfo data

* Add methods to update SignInfo to slashing keeper interface(/x/slashing/types/expected_keepers.go)
  • Loading branch information
sainoe authored and jtremback committed Feb 10, 2022
1 parent 2413b86 commit 58c103c
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 2 deletions.
10 changes: 10 additions & 0 deletions x/slashing/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import (
"github.com/cosmos/cosmos-sdk/x/slashing/types"
)

// Implements SlashingHooks interface
var _ types.SlashingHooks = Keeper{}

// AfterValidatorDowntime - call hook if registered
func (k Keeper) AfterValidatorDowntime(ctx sdk.Context, consAddress sdk.ConsAddress, power int64) {
if k.hooks != nil {
k.hooks.AfterValidatorDowntime(ctx, consAddress, power)
}
}

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)
Expand Down
7 changes: 6 additions & 1 deletion x/slashing/keeper/infractions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre

// fetch the validator public key
consAddr := sdk.ConsAddress(addr)

if _, err := k.GetPubkey(ctx, addr); err != nil {
panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr))
}
Expand Down Expand Up @@ -101,7 +102,7 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
// We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding.
signInfo.MissedBlocksCounter = 0
signInfo.IndexOffset = 0
k.clearValidatorMissedBlockBitArray(ctx, consAddr)
k.ClearValidatorMissedBlockBitArray(ctx, consAddr)

logger.Info(
"slashing and jailing validator due to liveness fault",
Expand All @@ -119,6 +120,10 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre
"validator", consAddr.String(),
)
}

// hook is triggered for each downtime detection
// and defered to keep safe the write operations on SignInfo
defer k.AfterValidatorDowntime(ctx, consAddr, power)
}

// Set the updated signing info
Expand Down
11 changes: 11 additions & 0 deletions x/slashing/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Keeper struct {
cdc codec.BinaryCodec
sk types.StakingKeeper
paramspace types.ParamSubspace
hooks types.SlashingHooks
}

// NewKeeper creates a slashing keeper
Expand Down Expand Up @@ -94,3 +95,13 @@ func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr cryptotypes.Addre
store := ctx.KVStore(k.storeKey)
store.Delete(types.AddrPubkeyRelationKey(addr))
}

func (k *Keeper) SetHooks(sh types.SlashingHooks) *Keeper {
if k.hooks != nil {
panic("cannot set validator hooks twice")
}

k.hooks = sh

return k
}
52 changes: 52 additions & 0 deletions x/slashing/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import (
"testing"
"time"

"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"

"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/slashing/testslashing"
"github.com/cosmos/cosmos-sdk/x/slashing/types"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/teststaking"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -276,3 +279,52 @@ func TestValidatorDippingInAndOut(t *testing.T) {
staking.EndBlocker(ctx, app.StakingKeeper)
tstaking.CheckValidator(valAddr, stakingtypes.Unbonding, true)
}

type MockSlashingHooks struct {
triggered bool
}

func (h *MockSlashingHooks) AfterValidatorDowntime(_ sdk.Context, _ sdk.ConsAddress, _ int64) {
h.triggered = true
}

// Test hook is triggered when validator is down
func TestValidatorDowntimedHook(t *testing.T) {
// initial setup
app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})

// create a validator pubkey and address
pubkey := ed25519.GenPrivKey().PubKey()
consAddr := sdk.ConsAddress(pubkey.Address())

// store the validator pubkey and signing info
app.SlashingKeeper.AddPubkey(ctx, pubkey)
valInfo := types.NewValidatorSigningInfo(consAddr, ctx.BlockHeight(), ctx.BlockHeight()-1,
time.Time{}.UTC(), false, int64(0))
app.SlashingKeeper.SetValidatorSigningInfo(ctx, consAddr, valInfo)

// define a slashing hook mock

mh := MockSlashingHooks{}
app.SlashingKeeper.SetHooks(&mh)

// 1000 first blocks OK
height := int64(0)
power := int64(1)

for ; height < app.SlashingKeeper.SignedBlocksWindow(ctx); height++ {
ctx = ctx.WithBlockHeight(height)
app.SlashingKeeper.HandleValidatorSignature(ctx, pubkey.Address(), power, true)
}
// hook shouldn't be triggered
require.False(t, mh.triggered)

// 501 blocks missed
for ; height < app.SlashingKeeper.SignedBlocksWindow(ctx)+(app.SlashingKeeper.SignedBlocksWindow(ctx)-app.SlashingKeeper.MinSignedPerWindow(ctx))+1; height++ {
ctx = ctx.WithBlockHeight(height)
app.SlashingKeeper.HandleValidatorSignature(ctx, pubkey.Address(), power, false)
}

require.True(t, mh.triggered)
}
2 changes: 1 addition & 1 deletion x/slashing/keeper/signing_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (k Keeper) SetValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.Con
}

// clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store
func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) {
func (k Keeper) ClearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitArrayPrefixKey(address))
defer iter.Close()
Expand Down
6 changes: 6 additions & 0 deletions x/slashing/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ type StakingHooks interface {

AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is bonded
}

// SlashingHooks event hooks for jailing and slashing validator
type SlashingHooks interface {
// Is triggered when the validator missed too many blocks
AfterValidatorDowntime(ctx sdk.Context, consAddr sdk.ConsAddress, power int64)
}

0 comments on commit 58c103c

Please sign in to comment.