Skip to main content

Evidence

ℹ️info

JMES's evidence module inherits from Cosmos SDK's evidence module. This document is a stub and mainly covers important JMES-specific notes on how it is used.

The evidence module allows arbitrary evidence of misbehaviour, such as equivocation and counterfactual signing, to be submitted and handled.

Typically, standard evidence handling expects the underlying consensus engine, Tendermint, to automatically submit evidence when it is discovered by allowing clients and foreign chains to submit more complex evidence directly. The evidence module operates differently.

All concrete evidence types must implement the Evidence interface contract. First, submitted Evidence is routed through the evidence module's Router, where it attempts to find a corresponding registered Handler for that specific Evidence type. Each Evidence type must have a Handler registered with the evidence module's keeper for it to be successfully routed and executed.

Each corresponding handler must also fulfill the Handler interface contract. The Handler for a given Evidence type can perform any arbitrary state transitions, such as slashing, jailing, and tombstoning.

Concepts

Evidence

Any concrete type of evidence submitted to the module must fulfill the following Evidence contract. Not all concrete types of evidence will fulfill this contract in the same way and some data might be entirely irrelevant to certain types of evidence. An additional ValidatorEvidence, which extends Evidence, has also been created to define a contract for evidence against malicious validators.


_29
// Evidence defines the contract which concrete evidence types of misbehaviour
_29
// must implement.
_29
type Evidence interface {
_29
proto.Message
_29
_29
Route() string
_29
Type() string
_29
String() string
_29
Hash() tmbytes.HexBytes
_29
ValidateBasic() error
_29
_29
// Height at which the infraction occurred
_29
GetHeight() int64
_29
}
_29
_29
// ValidatorEvidence extends Evidence interface to define contract
_29
// for evidence against malicious validators
_29
type ValidatorEvidence interface {
_29
Evidence
_29
_29
// The consensus address of the malicious validator at time of infraction
_29
GetConsensusAddress() sdk.ConsAddress
_29
_29
// The total power of the malicious validator at time of infraction
_29
GetValidatorPower() int64
_29
_29
// The total validator set power at time of infraction
_29
GetTotalPower() int64
_29
}

Registration and handling

First, the evidence module must know about all the types of evidence it is expected to handle. Register the Route method in the Evidence contract with a Router as defined below. The Router accepts Evidence and attempts to find the corresponding Handler for the Evidence via the Route method.


_7
type Router interface {
_7
AddRoute(r string, h Handler) Router
_7
HasRoute(r string) bool
_7
GetRoute(path string) Handler
_7
Seal()
_7
Sealed() bool
_7
}

As defined below, the Handler is responsible for executing the entirety of the business logic for handling Evidence. Doing so typically includes validating the evidence, both stateless checks via ValidateBasic and stateful checks via any keepers provided to the Handler. Additionally, the Handler may also perform capabilities, such as slashing and jailing a validator. All Evidence handled by the Handler must be persisted.


_5
// Handler defines an agnostic Evidence handler. The handler is responsible
_5
// for executing all corresponding business logic necessary for verifying the
_5
// evidence as valid. In addition, the Handler may execute any necessary
_5
// slashing and potential jailing.
_5
type Handler func(sdk.Context, Evidence) error

State

The evidence module only stores valid submitted Evidence in state. The evidence state is also stored and exported in the evidence module's GenesisState.


_5
// GenesisState defines the evidence module's genesis state.
_5
message GenesisState {
_5
// evidence defines all the evidence at genesis.
_5
repeated google.protobuf.Any evidence = 1;
_5
}

Messages

MsgSubmitEvidence

Evidence is submitted through a MsgSubmitEvidence message:


_6
// MsgSubmitEvidence represents a message that supports submitting arbitrary
_6
// Evidence of misbehaviour such as equivocation or counterfactual signing.
_6
message MsgSubmitEvidence {
_6
string submitter = 1;
_6
google.protobuf.Any evidence = 2;
_6
}

The Evidence of a MsgSubmitEvidence message must have a corresponding Handler registered with the evidence module's Router to be processed and routed correctly.

Given the Evidence is registered with a corresponding Handler, it is processed as follows:


_23
func SubmitEvidence(ctx Context, evidence Evidence) error {
_23
if _, ok := GetEvidence(ctx, evidence.Hash()); ok {
_23
return sdkerrors.Wrap(types.ErrEvidenceExists, evidence.Hash().String())
_23
}
_23
if !router.HasRoute(evidence.Route()) {
_23
return sdkerrors.Wrap(types.ErrNoEvidenceHandlerExists, evidence.Route())
_23
}
_23
_23
handler := router.GetRoute(evidence.Route())
_23
if err := handler(ctx, evidence); err != nil {
_23
return sdkerrors.Wrap(types.ErrInvalidEvidence, err.Error())
_23
}
_23
_23
ctx.EventManager().EmitEvent(
_23
sdk.NewEvent(
_23
types.EventTypeSubmitEvidence,
_23
sdk.NewAttribute(types.AttributeKeyEvidenceHash, evidence.Hash().String()),
_23
),
_23
)
_23
_23
SetEvidence(ctx, evidence)
_23
return nil
_23
}

Valid submitted Evidence of the same type must not already exist. The Evidence is routed to the Handler and executed. If no error occurs when handling the Evidence, an event is emitted and it is persisted to state.

Events

The evidence module emits the following handler events:

MsgSubmitEvidence

TypeAttribute KeyAttribute Value
submit_evidenceevidence_hash{evidenceHash}
messagemoduleevidence
messagesender{senderAddress}
messageactionsubmit_evidence

BeginBlock

Evidence handling

Tendermint blocks can include Evidence that indicates whether a validator acted maliciously. The relevant information is forwarded to the application as ABCI Evidence in abci.RequestBeginBlock so that the validator can be punished accordingly.

Equivocation

Currently, the SDK handles two types of evidence inside the ABCI BeginBlock:

  • DuplicateVoteEvidence,
  • LightClientAttackEvidence.

The evidence module handles these two evidence types the same way. First, the SDK converts the Tendermint concrete evidence type to a SDK Evidence interface by using Equivocation as the concrete type.


_7
// Equivocation implements the Evidence interface.
_7
message Equivocation {
_7
int64 height = 1;
_7
google.protobuf.Timestamp time = 2;
_7
int64 power = 3;
_7
string consensus_address = 4;
_7
}

For an Equivocation submitted in block to be valid, it must meet the following requirement:

Evidence.Timestamp >= block.Timestamp - MaxEvidenceAge

where:

  • Evidence.Timestamp is the timestamp in the block at height Evidence.Height.
  • block.Timestamp is the current block timestamp.

If valid Equivocation evidence is included in a block, the validator's stake is reduced by SlashFractionDoubleSign, as defined by the slashing module. The reduction is implemented at the point when the infraction occurred instead of when the evidence was discovered. The stake that contributed to the infraction is slashed, even if it has been redelegated or started unbonding.

Additionally, the validator is permanently jailed and tombstoned so that the validator cannot re-enter the validator set again.

Equivocation evidence handling code


_97
func (k Keeper) HandleEquivocationEvidence(ctx sdk.Context, evidence *types.Equivocation) {
_97
logger := k.Logger(ctx)
_97
consAddr := evidence.GetConsensusAddress()
_97
_97
if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
_97
// Ignore evidence that cannot be handled.
_97
//
_97
// NOTE: Developers used to panic with:
_97
// `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
_97
// but this couples the expectations of the app to both Tendermint and
_97
// the simulator. Both are expected to provide the full range of
_97
// allowable, but none of the disallowed evidence types. Instead of
_97
// getting this coordination right, it is easier to relax the
_97
// constraints and ignore evidence that cannot be handled.
_97
return
_97
}
_97
_97
// calculate the age of the evidence
_97
infractionHeight := evidence.GetHeight()
_97
infractionTime := evidence.GetTime()
_97
ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
_97
ageBlocks := ctx.BlockHeader().Height - infractionHeight
_97
_97
// Reject evidence if the double-sign is too old. Evidence is considered stale
_97
// if the difference in time and number of blocks is greater than the allowed
_97
// parameters defined.
_97
cp := ctx.ConsensusParams()
_97
if cp != nil && cp.Evidence != nil {
_97
if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
_97
logger.Info(
_97
"ignored equivocation; evidence too old",
_97
"validator", consAddr,
_97
"infraction_height", infractionHeight,
_97
"max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
_97
"infraction_time", infractionTime,
_97
"max_age_duration", cp.Evidence.MaxAgeDuration,
_97
)
_97
return
_97
}
_97
}
_97
_97
validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr)
_97
if validator == nil || validator.IsUnbonded() {
_97
// Defensive: Simulation doesn't take unbonding periods into account, and
_97
// Tendermint might break this assumption at some point.
_97
return
_97
}
_97
_97
if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok {
_97
panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
_97
}
_97
_97
// ignore if the validator is already tombstoned
_97
if k.slashingKeeper.IsTombstoned(ctx, consAddr) {
_97
logger.Info(
_97
"ignored equivocation; validator already tombstoned",
_97
"validator", consAddr,
_97
"infraction_height", infractionHeight,
_97
"infraction_time", infractionTime,
_97
)
_97
return
_97
}
_97
_97
logger.Info(
_97
"confirmed equivocation",
_97
"validator", consAddr,
_97
"infraction_height", infractionHeight,
_97
"infraction_time", infractionTime,
_97
)
_97
_97
// To retrieve the stake distribution which signed the block, subtract ValidatorUpdateDelay from the evidence height.
_97
// Note, that this *can* result in a negative "distributionHeight", up to
_97
// -ValidatorUpdateDelay, i.e. at the end of the
_97
// pre-genesis block (none) = at the beginning of the genesis block.
_97
// That's fine since this is just used to filter unbonding delegations & redelegations.
_97
distributionHeight := infractionHeight - sdk.ValidatorUpdateDelay
_97
_97
// Slash validator. The `power` is the int64 power of the validator as provided
_97
// to/by Tendermint. This value is validator.Tokens as sent to Tendermint via
_97
// ABCI and now received as evidence. The fraction is passed in separately
_97
// to slash unbonding and rebonding delegations.
_97
k.slashingKeeper.Slash(
_97
ctx,
_97
consAddr,
_97
k.slashingKeeper.SlashFractionDoubleSign(ctx),
_97
evidence.GetValidatorPower(), distributionHeight,
_97
)
_97
_97
// Jail the validator if not already jailed. This will begin unbonding the
_97
// validator if not already unbonding (tombstoned).
_97
if !validator.IsJailed() {
_97
k.slashingKeeper.Jail(ctx, consAddr)
_97
}
_97
_97
k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime)
_97
k.slashingKeeper.Tombstone(ctx, consAddr)
_97
}

The slashing, jailing, and tombstoning calls are delegated through the slashing module, which emits informative events and finally delegates calls to the staking module. For more information about slashing and jailing, see transitions.

Parameters

The genesis parameters outlined in the Genesis Builder Script are as follows:


_6
# Consensus Params: Evidence
_6
genesis['consensus_params']['evidence'] = {
_6
'max_age_num_blocks': '100000',
_6
'max_age_duration': '172800000000000',
_6
'max_bytes': '1000000'
_6
}