2018-05-23 13:25:56 -07:00
package slashing
import (
"fmt"
2018-08-12 00:33:48 -07:00
"time"
tmtypes "github.com/tendermint/tendermint/types"
2018-05-23 13:25:56 -07:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
2018-07-13 17:12:23 -07:00
"github.com/cosmos/cosmos-sdk/x/params"
2018-08-12 00:33:48 -07:00
abci "github.com/tendermint/tendermint/abci/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
cdc * wire . Codec
validatorSet sdk . ValidatorSet
2018-07-13 17:12:23 -07:00
params params . Getter
2018-05-23 13:25:56 -07:00
// codespace
codespace sdk . CodespaceType
}
// NewKeeper creates a slashing keeper
2018-07-13 17:12:23 -07:00
func NewKeeper ( cdc * wire . 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 ,
2018-07-13 17:12:23 -07:00
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" )
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-08-31 17:01:23 -07:00
address := sdk . ConsAddress ( addr )
2018-08-21 06:40:43 -07:00
pubkey , err := k . getPubkey ( ctx , addr )
if err != nil {
panic ( fmt . Sprintf ( "Validator address %v not found" , addr ) )
}
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-08-31 17:01:23 -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 , address , fraction , infractionHeight )
logger . Info ( fmt . Sprintf ( "Fraction slashed capped by slashing period from %v to %v" , fraction , revisedFraction ) )
2018-06-29 20:34:55 -07:00
// Slash validator
2018-08-31 17:01:23 -07:00
k . validatorSet . Slash ( ctx , pubkey , infractionHeight , power , revisedFraction )
2018-06-29 20:34:55 -07:00
// Jail validator
2018-08-22 08:56:13 -07:00
k . validatorSet . Jail ( ctx , pubkey )
// Set validator jail duration
2018-06-29 20:34:55 -07:00
signInfo , found := k . getValidatorSigningInfo ( ctx , address )
if ! found {
panic ( fmt . Sprintf ( "Expected signing info for validator %s but not found" , address ) )
}
2018-08-12 00:33:48 -07:00
signInfo . JailedUntil = time . Add ( k . DoubleSignUnbondDuration ( ctx ) )
2018-06-29 20:34:55 -07:00
k . setValidatorSigningInfo ( ctx , address , signInfo )
2018-05-23 13:25:56 -07:00
}
// handle a validator signature, must be called once per validator per block
2018-07-24 19:12:48 -07:00
// nolint gocyclo
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-08-31 17:01:23 -07:00
address := sdk . ConsAddress ( addr )
2018-08-12 00:33:48 -07:00
pubkey , err := k . getPubkey ( ctx , addr )
if err != nil {
panic ( fmt . Sprintf ( "Validator address %v not found" , addr ) )
}
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-06-10 17:55:54 -07:00
signInfo , found := k . getValidatorSigningInfo ( ctx , address )
if ! found {
2018-06-11 12:47:35 -07:00
// If this validator has never been seen before, construct a new SigningInfo with the correct start height
2018-08-12 00:33:48 -07:00
signInfo = NewValidatorSigningInfo ( height , 0 , time . Unix ( 0 , 0 ) , 0 )
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-05-23 13:25:56 -07:00
previous := k . getValidatorSigningBitArray ( ctx , address , 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
2018-05-23 13:25:56 -07:00
k . setValidatorSigningBitArray ( ctx , address , index , false )
signInfo . SignedBlocksCounter --
} else if ! previous && signed {
2018-05-30 15:32:08 -07:00
// Array value has changed from unsigned to signed, increment counter
2018-05-23 13:25:56 -07:00
k . setValidatorSigningBitArray ( ctx , address , index , true )
signInfo . SignedBlocksCounter ++
}
2018-05-28 15:10:52 -07:00
2018-06-29 20:34:55 -07:00
if ! signed {
2018-08-12 00:33:48 -07:00
logger . Info ( fmt . Sprintf ( "Absent validator %s at height %d, %d signed, threshold %d" , addr , height , signInfo . SignedBlocksCounter , 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 )
if height > minHeight && signInfo . SignedBlocksCounter < k . MinSignedPerWindow ( ctx ) {
2018-07-24 19:12:48 -07:00
validator := k . validatorSet . ValidatorByPubKey ( ctx , pubkey )
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 ) ) )
k . validatorSet . Slash ( ctx , pubkey , height , power , k . SlashFractionDowntime ( ctx ) )
2018-08-22 08:56:13 -07:00
k . validatorSet . Jail ( ctx , pubkey )
2018-08-12 00:33:48 -07:00
signInfo . JailedUntil = ctx . BlockHeader ( ) . Time . Add ( k . DowntimeUnbondDuration ( ctx ) )
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
k . setValidatorSigningInfo ( ctx , address , signInfo )
2018-05-23 13:25:56 -07:00
}
2018-08-12 00:33:48 -07:00
// AddValidators adds the validators to the keepers validator addr to pubkey mapping.
func ( k Keeper ) AddValidators ( ctx sdk . Context , vals [ ] abci . Validator ) {
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 ) )
}