cosmos-sdk/x/slashing/keeper.go

184 lines
7.2 KiB
Go
Raw Normal View History

2018-05-23 13:25:56 -07:00
package slashing
import (
"fmt"
"time"
tmtypes "github.com/tendermint/tendermint/types"
2018-05-23 13:25:56 -07:00
"github.com/cosmos/cosmos-sdk/codec"
2018-05-23 13:25:56 -07:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
2018-05-23 13:25:56 -07:00
)
// Keeper of the slashing store
type Keeper struct {
2018-06-01 15:27:37 -07:00
storeKey sdk.StoreKey
cdc *codec.Codec
2018-06-01 15:27:37 -07:00
validatorSet sdk.ValidatorSet
params params.Getter
2018-05-23 13:25:56 -07:00
// codespace
codespace sdk.CodespaceType
}
// NewKeeper creates a slashing keeper
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, params params.Getter, codespace sdk.CodespaceType) Keeper {
2018-05-23 13:25:56 -07:00
keeper := Keeper{
2018-06-01 15:27:37 -07:00
storeKey: key,
cdc: cdc,
validatorSet: vs,
params: params,
2018-06-01 15:27:37 -07:00
codespace: codespace,
2018-05-23 13:25:56 -07:00
}
return keeper
}
// handle a validator signing two blocks at the same height
2018-08-21 06:40:43 -07:00
func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) {
2018-05-23 13:25:56 -07:00
logger := ctx.Logger().With("module", "x/slashing")
time := ctx.BlockHeader().Time
age := time.Sub(timestamp)
consAddr := sdk.ConsAddress(addr)
2018-08-21 06:40:43 -07:00
pubkey, err := k.getPubkey(ctx, addr)
if err != nil {
panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))
2018-08-21 06:40:43 -07:00
}
2018-05-28 15:10:52 -07:00
// Double sign too old
maxEvidenceAge := k.MaxEvidenceAge(ctx)
if age > maxEvidenceAge {
logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge))
2018-05-23 13:25:56 -07:00
return
}
2018-05-28 15:10:52 -07:00
// Double sign confirmed
logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge))
// Cap the amount slashed to the penalty for the worst infraction
// within the slashing period when this infraction was committed
fraction := k.SlashFractionDoubleSign(ctx)
revisedFraction := k.capBySlashingPeriod(ctx, consAddr, fraction, infractionHeight)
logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction))
2018-09-06 21:56:05 -07:00
// We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height.
distributionHeight := infractionHeight - ValidatorUpdateDelay
// Slash validator
2018-10-03 08:59:01 -07:00
k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, revisedFraction)
// Jail validator
k.validatorSet.Jail(ctx, consAddr)
2018-08-22 08:56:13 -07:00
// Set validator jail duration
signInfo, found := k.getValidatorSigningInfo(ctx, consAddr)
if !found {
panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr))
}
signInfo.JailedUntil = time.Add(k.DoubleSignUnbondDuration(ctx))
k.setValidatorSigningInfo(ctx, consAddr, signInfo)
2018-05-23 13:25:56 -07:00
}
// 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
func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) {
2018-05-23 13:25:56 -07:00
logger := ctx.Logger().With("module", "x/slashing")
height := ctx.BlockHeight()
consAddr := sdk.ConsAddress(addr)
pubkey, err := k.getPubkey(ctx, addr)
if err != nil {
panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))
}
2018-05-28 15:10:52 -07:00
// Local index, so counts blocks validator *should* have signed
2018-06-10 18:03:52 -07:00
// Will use the 0-value default signing info if not present, except for start height
signInfo, found := k.getValidatorSigningInfo(ctx, consAddr)
if !found {
// If this validator has never been seen before, construct a new SigningInfo with the correct start height
signInfo = NewValidatorSigningInfo(height, 0, time.Unix(0, 0), 0)
}
index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx)
signInfo.IndexOffset++
2018-05-28 15:10:52 -07:00
// Update signed block bit array & counter
2018-05-30 15:32:08 -07:00
// 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.getValidatorSigningBitArray(ctx, consAddr, index)
2018-05-28 17:48:29 -07:00
if previous == signed {
2018-05-30 15:32:08 -07:00
// Array value at this index has not changed, no need to update counter
2018-05-28 17:48:29 -07:00
} else if previous && !signed {
2018-05-30 15:32:08 -07:00
// Array value has changed from signed to unsigned, decrement counter
k.setValidatorSigningBitArray(ctx, consAddr, index, false)
2018-05-23 13:25:56 -07:00
signInfo.SignedBlocksCounter--
} else if !previous && signed {
2018-05-30 15:32:08 -07:00
// Array value has changed from unsigned to signed, increment counter
k.setValidatorSigningBitArray(ctx, consAddr, index, true)
2018-05-23 13:25:56 -07:00
signInfo.SignedBlocksCounter++
}
2018-05-28 15:10:52 -07:00
if !signed {
logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d signed, threshold %d", addr, height, signInfo.SignedBlocksCounter, k.MinSignedPerWindow(ctx)))
}
minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx)
if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) {
validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr)
2018-08-22 08:56:13 -07:00
if validator != nil && !validator.GetJailed() {
// Downtime confirmed: slash and jail the validator
logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d",
pubkey.Address(), minHeight, k.MinSignedPerWindow(ctx)))
2018-09-06 21:56:05 -07:00
// We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height,
// and subtract an additional 1 since this is the LastCommit.
distributionHeight := height - ValidatorUpdateDelay - 1
2018-10-03 08:59:01 -07:00
k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx))
k.validatorSet.Jail(ctx, consAddr)
signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx))
} else {
2018-08-22 08:56:13 -07:00
// Validator was (a) not found or (b) already jailed, don't slash
logger.Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed",
pubkey.Address()))
}
2018-05-23 13:25:56 -07:00
}
2018-05-28 17:35:42 -07:00
// Set the updated signing info
k.setValidatorSigningInfo(ctx, consAddr, signInfo)
2018-05-23 13:25:56 -07:00
}
// AddValidators adds the validators to the keepers validator addr to pubkey mapping.
2018-09-03 09:04:28 -07:00
func (k Keeper) AddValidators(ctx sdk.Context, vals []abci.ValidatorUpdate) {
for i := 0; i < len(vals); i++ {
val := vals[i]
pubkey, err := tmtypes.PB2TM.PubKey(val.PubKey)
if err != nil {
panic(err)
}
k.addPubkey(ctx, pubkey)
}
}
// TODO: Make a method to remove the pubkey from the map when a validator is unbonded.
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.UnmarshalBinary(store.Get(getAddrPubkeyRelationKey(address)), &pubkey)
if err != nil {
return nil, fmt.Errorf("address %v not found", 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.MustMarshalBinary(pubkey)
store.Set(getAddrPubkeyRelationKey(addr), bz)
}
func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) {
store := ctx.KVStore(k.storeKey)
store.Delete(getAddrPubkeyRelationKey(addr))
}