110 lines
3.9 KiB
Go
110 lines
3.9 KiB
Go
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()
|
|
infractionHeight := evidence.GetHeight()
|
|
|
|
// calculate the age of the evidence
|
|
blockTime := ctx.BlockHeader().Time
|
|
age := blockTime.Sub(evidence.GetTime())
|
|
|
|
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
|
|
}
|
|
|
|
// reject evidence if the double-sign is too old
|
|
if age > k.MaxEvidenceAge(ctx) {
|
|
logger.Info(
|
|
fmt.Sprintf(
|
|
"ignored double sign from %s at height %d, age of %d past max age of %d",
|
|
consAddr, infractionHeight, age, k.MaxEvidenceAge(ctx),
|
|
),
|
|
)
|
|
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(
|
|
fmt.Sprintf(
|
|
"ignored double sign from %s at height %d, validator already tombstoned",
|
|
consAddr, infractionHeight,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
logger.Info(fmt.Sprintf("confirmed double sign from %s at height %d, age of %d", consAddr, infractionHeight, age))
|
|
|
|
// 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)
|
|
}
|