Evidence
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._29type 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_29type 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.
_7type 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._5type 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._5message 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._6message 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:
_23func 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
Type | Attribute Key | Attribute Value |
---|---|---|
submit_evidence | evidence_hash | {evidenceHash} |
message | module | evidence |
message | sender | {senderAddress} |
message | action | submit_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._7message 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 heightEvidence.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
_97func (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 }