123 lines
4.4 KiB
Go
123 lines
4.4 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/x/evidence/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 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.Slash(
|
|
ctx,
|
|
consAddr,
|
|
k.slashingKeeper.SlashFractionDoubleSign(ctx),
|
|
evidence.GetValidatorPower(), distributionHeight,
|
|
)
|
|
|
|
// 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)
|
|
}
|