package keeper import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/evidence/types" ) // HandleDoubleSign 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) HandleDoubleSign(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) }