2018-05-23 13:25:56 -07:00
package slashing
import (
"fmt"
2018-08-12 00:33:48 -07:00
"time"
2018-09-13 11:17:32 -07:00
"github.com/cosmos/cosmos-sdk/codec"
2018-05-23 13:25:56 -07:00
sdk "github.com/cosmos/cosmos-sdk/types"
2018-07-13 17:12:23 -07:00
"github.com/cosmos/cosmos-sdk/x/params"
2018-10-05 10:56:17 -07:00
stake "github.com/cosmos/cosmos-sdk/x/stake/types"
2018-06-28 17:54:47 -07:00
"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
2018-09-13 11:17:32 -07:00
cdc * codec . Codec
2018-06-01 15:27:37 -07:00
validatorSet sdk . ValidatorSet
2018-10-10 13:01:30 -07:00
paramspace params . Subspace
2018-08-31 02:03:43 -07:00
2018-05-23 13:25:56 -07:00
// codespace
codespace sdk . CodespaceType
}
// NewKeeper creates a slashing keeper
2018-10-10 13:01:30 -07:00
func NewKeeper ( cdc * codec . Codec , key sdk . StoreKey , vs sdk . ValidatorSet , paramspace params . Subspace , 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 ,
2018-10-10 13:01:30 -07:00
paramspace : paramspace . WithTypeTable ( ParamTypeTable ( ) ) ,
2018-06-01 15:27:37 -07:00
codespace : codespace ,
2018-05-23 13:25:56 -07:00
}
return keeper
}
2018-10-23 11:33:39 -07:00
// handle a validator signing two blocks at the same height
// power: power of the double-signing validator at the height of infraction
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" )
2018-06-29 20:34:55 -07:00
time := ctx . BlockHeader ( ) . Time
2018-08-12 00:33:48 -07:00
age := time . Sub ( timestamp )
2018-09-24 21:09:31 -07:00
consAddr := sdk . ConsAddress ( addr )
2018-08-21 06:40:43 -07:00
pubkey , err := k . getPubkey ( ctx , addr )
if err != nil {
2018-09-24 21:09:31 -07:00
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
2018-11-04 22:11:03 -08:00
// Get validator.
validator := k . validatorSet . ValidatorByConsAddr ( ctx , consAddr )
if validator == nil || validator . GetStatus ( ) == sdk . Unbonded {
// Defensive.
// Simulation doesn't take unbonding periods into account, and
// Tendermint might break this assumption at some point.
return
}
2018-05-28 15:10:52 -07:00
// Double sign too old
2018-07-13 17:12:23 -07:00
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
2018-07-13 17:12:23 -07:00
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 ) )
2018-06-29 20:34:55 -07:00
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.
2018-10-05 11:00:01 -07:00
// Note that this *can* result in a negative "distributionHeight", up to -ValidatorUpdateDelay,
2018-10-04 09:22:51 -07:00
// i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block.
// That's fine since this is just used to filter unbonding delegations & redelegations.
2018-10-05 10:56:17 -07:00
distributionHeight := infractionHeight - stake . ValidatorUpdateDelay
2018-09-06 21:56:05 -07:00
2018-10-05 05:11:36 -07:00
// 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 , distributionHeight )
logger . Info ( fmt . Sprintf ( "Fraction slashed capped by slashing period from %v to %v" , fraction , revisedFraction ) )
2018-10-23 11:33:39 -07:00
// Slash validator
2018-10-22 01:18:10 -07:00
// `power` is the int64 power of the validator as provided to/by
2018-10-23 11:33:39 -07:00
// Tendermint. This value is validator.Tokens as sent to Tendermint via
2018-10-22 01:18:10 -07:00
// ABCI, and now received as evidence.
// The revisedFraction (which is the new fraction to be slashed) is passed
// in separately to separately slash unbonding and rebonding delegations.
2018-10-03 08:59:01 -07:00
k . validatorSet . Slash ( ctx , consAddr , distributionHeight , power , revisedFraction )
2018-06-29 20:34:55 -07:00
2018-10-05 05:11:36 -07:00
// Jail validator if not already jailed
if ! validator . GetJailed ( ) {
k . validatorSet . Jail ( ctx , consAddr )
}
2018-08-22 08:56:13 -07:00
2018-10-05 10:42:52 -07:00
// Set or updated validator jail duration
2018-09-24 21:09:31 -07:00
signInfo , found := k . getValidatorSigningInfo ( ctx , consAddr )
2018-06-29 20:34:55 -07:00
if ! found {
2018-09-24 21:09:31 -07:00
panic ( fmt . Sprintf ( "Expected signing info for validator %s but not found" , consAddr ) )
2018-06-29 20:34:55 -07:00
}
2018-08-12 00:33:48 -07:00
signInfo . JailedUntil = time . Add ( k . DoubleSignUnbondDuration ( ctx ) )
2018-11-26 04:21:23 -08:00
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
2018-09-24 21:09:31 -07:00
// TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too
2018-08-12 00:33:48 -07:00
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 ( )
2018-09-24 21:09:31 -07:00
consAddr := sdk . ConsAddress ( addr )
2018-08-12 00:33:48 -07:00
pubkey , err := k . getPubkey ( ctx , addr )
if err != nil {
2018-09-24 21:09:31 -07:00
panic ( fmt . Sprintf ( "Validator consensus-address %v not found" , consAddr ) )
2018-08-12 00:33:48 -07:00
}
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
2018-09-24 21:09:31 -07:00
signInfo , found := k . getValidatorSigningInfo ( ctx , consAddr )
2018-06-10 17:55:54 -07:00
if ! found {
2018-10-11 13:48:00 -07:00
panic ( fmt . Sprintf ( "Expected signing info for validator %s but not found" , consAddr ) )
2018-06-10 17:55:54 -07:00
}
2018-07-13 17:12:23 -07:00
index := signInfo . IndexOffset % k . SignedBlocksWindow ( ctx )
2018-05-28 14:46:08 -07:00
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
2018-10-15 12:11:32 -07:00
previous := k . getValidatorMissedBlockBitArray ( ctx , consAddr , index )
2018-10-11 15:04:44 -07:00
missed := ! signed
2018-10-15 14:58:39 -07:00
switch {
case ! previous && missed :
2018-10-11 15:04:44 -07:00
// Array value has changed from not missed to missed, increment counter
2018-10-15 12:11:32 -07:00
k . setValidatorMissedBlockBitArray ( ctx , consAddr , index , true )
2018-10-11 15:04:44 -07:00
signInfo . MissedBlocksCounter ++
2018-10-15 14:58:39 -07:00
case previous && ! missed :
2018-10-11 15:04:44 -07:00
// Array value has changed from missed to not missed, decrement counter
2018-10-15 12:11:32 -07:00
k . setValidatorMissedBlockBitArray ( ctx , consAddr , index , false )
2018-10-11 15:04:44 -07:00
signInfo . MissedBlocksCounter --
2018-10-15 14:58:39 -07:00
default :
// Array value at this index has not changed, no need to update counter
2018-05-23 13:25:56 -07:00
}
2018-05-28 15:10:52 -07:00
2018-10-11 15:04:44 -07:00
if missed {
logger . Info ( fmt . Sprintf ( "Absent validator %s at height %d, %d missed, threshold %d" , addr , height , signInfo . MissedBlocksCounter , k . MinSignedPerWindow ( ctx ) ) )
2018-06-29 20:34:55 -07:00
}
2018-07-13 17:12:23 -07:00
minHeight := signInfo . StartHeight + k . SignedBlocksWindow ( ctx )
2018-10-11 15:04:44 -07:00
maxMissed := k . SignedBlocksWindow ( ctx ) - k . MinSignedPerWindow ( ctx )
2018-10-12 12:15:39 -07:00
if height > minHeight && signInfo . MissedBlocksCounter > maxMissed {
2018-09-24 21:09:31 -07:00
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
2018-07-24 19:12:48 -07:00
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.
2018-10-05 11:00:01 -07:00
// Note that this *can* result in a negative "distributionHeight" up to -ValidatorUpdateDelay-1,
2018-10-04 09:22:51 -07:00
// i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block.
// That's fine since this is just used to filter unbonding delegations & redelegations.
2018-10-05 10:56:17 -07:00
distributionHeight := height - stake . ValidatorUpdateDelay - 1
2018-10-03 08:59:01 -07:00
k . validatorSet . Slash ( ctx , consAddr , distributionHeight , power , k . SlashFractionDowntime ( ctx ) )
2018-09-24 21:09:31 -07:00
k . validatorSet . Jail ( ctx , consAddr )
2018-08-12 00:33:48 -07:00
signInfo . JailedUntil = ctx . BlockHeader ( ) . Time . Add ( k . DowntimeUnbondDuration ( ctx ) )
2018-10-15 12:44:23 -07:00
// 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 )
2018-07-24 19:12:48 -07:00
} 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" ,
2018-07-24 19:12:48 -07:00
pubkey . Address ( ) ) )
}
2018-05-23 13:25:56 -07:00
}
2018-05-28 17:35:42 -07:00
// Set the updated signing info
2018-11-26 04:21:23 -08:00
k . SetValidatorSigningInfo ( ctx , consAddr , signInfo )
2018-05-23 13:25:56 -07:00
}
2018-08-12 00:33:48 -07:00
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
2018-11-04 18:28:38 -08:00
err := k . cdc . UnmarshalBinaryLengthPrefixed ( store . Get ( getAddrPubkeyRelationKey ( address ) ) , & pubkey )
2018-08-12 00:33:48 -07:00
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 )
2018-11-04 18:28:38 -08:00
bz := k . cdc . MustMarshalBinaryLengthPrefixed ( pubkey )
2018-08-12 00:33:48 -07:00
store . Set ( getAddrPubkeyRelationKey ( addr ) , bz )
}
func ( k Keeper ) deleteAddrPubkeyRelation ( ctx sdk . Context , addr crypto . Address ) {
store := ctx . KVStore ( k . storeKey )
store . Delete ( getAddrPubkeyRelationKey ( addr ) )
}