package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
/ HandleEquivocationEvidence implements an equivocation evidence handler. Assuming the
/ evidence is valid, the validator committing the misbehavior will be slashed,
/ jailed and tombstoned. Once tombstoned, the validator will not be able to
/ recover. Note, the evidence contains the block time and height at the time of
/ the equivocation.
/
/ The evidence is considered invalid if:
/ - the evidence is too old
/ - the validator is unbonded or does not exist
/ - the signing info does not exist (will panic)
/ - is already tombstoned
/
/ TODO: Some of the invalid constraints listed above may need to be reconsidered
/ in the case of a lunatic attack.
func (k Keeper)
HandleEquivocationEvidence(ctx sdk.Context, evidence *types.Equivocation) {
logger := k.Logger(ctx)
consAddr := evidence.GetConsensusAddress()
if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
/ Ignore evidence that cannot be handled.
/
/ NOTE: We used to panic with:
/ `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
/ but this couples the expectations of the app to both Tendermint and
/ the simulator. Both are expected to provide the full range of
/ allowable but none of the disallowed evidence types. Instead of
/ getting this coordination right, it is easier to relax the
/ constraints and ignore evidence that cannot be handled.
return
}
/ calculate the age of the evidence
infractionHeight := evidence.GetHeight()
infractionTime := evidence.GetTime()
ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
ageBlocks := ctx.BlockHeader().Height - infractionHeight
/ Reject evidence if the double-sign is too old. Evidence is considered stale
/ if the difference in time and number of blocks is greater than the allowed
/ parameters defined.
cp := ctx.ConsensusParams()
if cp != nil && cp.Evidence != nil {
if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
logger.Info(
"ignored equivocation; evidence too old",
"validator", consAddr,
"infraction_height", infractionHeight,
"max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
"infraction_time", infractionTime,
"max_age_duration", cp.Evidence.MaxAgeDuration,
)
return
}
}
validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr)
if validator == nil || validator.IsUnbonded() {
/ Defensive: Simulation doesn't take unbonding periods into account, and
/ Tendermint might break this assumption at some point.
return
}
if !validator.GetOperator().Empty() {
if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
/ Ignore evidence that cannot be handled.
/
/ NOTE: We used to panic with:
/ `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
/ but this couples the expectations of the app to both Tendermint and
/ the simulator. Both are expected to provide the full range of
/ allowable but none of the disallowed evidence types. Instead of
/ getting this coordination right, it is easier to relax the
/ constraints and ignore evidence that cannot be handled.
return
}
}
if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok {
panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
}
/ ignore if the validator is already tombstoned
if k.slashingKeeper.IsTombstoned(ctx, consAddr) {
logger.Info(
"ignored equivocation; validator already tombstoned",
"validator", consAddr,
"infraction_height", infractionHeight,
"infraction_time", infractionTime,
)
return
}
logger.Info(
"confirmed equivocation",
"validator", consAddr,
"infraction_height", infractionHeight,
"infraction_time", infractionTime,
)
/ We need to retrieve the stake distribution which signed the block, so we
/ subtract ValidatorUpdateDelay from the evidence height.
/ Note, that this *can* result in a negative "distributionHeight", up to
/ -ValidatorUpdateDelay, 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.
distributionHeight := infractionHeight - sdk.ValidatorUpdateDelay
/ Slash validator. The `power` is the int64 power of the validator as provided
/ to/by Tendermint. This value is validator.Tokens as sent to Tendermint via
/ ABCI, and now received as evidence. The fraction is passed in to separately
/ to slash unbonding and rebonding delegations.
k.slashingKeeper.SlashWithInfractionReason(
ctx,
consAddr,
k.slashingKeeper.SlashFractionDoubleSign(ctx),
evidence.GetValidatorPower(), distributionHeight,
stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN,
)
/ Jail the validator if not already jailed. This will begin unbonding the
/ validator if not already unbonding (tombstoned).
if !validator.IsJailed() {
k.slashingKeeper.Jail(ctx, consAddr)
}
k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime)
k.slashingKeeper.Tombstone(ctx, consAddr)
k.SetEvidence(ctx, evidence)
}