概述

x/evidence 是 Cosmos SDK 模块的一种实现,遵循 ADR 009, 允许提交并处理任意类型的作恶证据, 例如双重签名和反事实签名。 证据模块不同于标准的证据处理方式。标准方式通常依赖底层共识引擎(例如 CometBFT)在发现证据时自动提交; 而证据模块允许客户端和外部链直接提交更复杂的证据。 所有具体证据类型都必须实现 Evidence 接口约定。提交的 Evidence 会首先经过证据模块的 Router 路由,在此过程中它会尝试为该特定 Evidence 类型查找已注册的对应 Handler。 每种 Evidence 类型都必须在证据模块 keeper 中注册一个 Handler, 这样才能被成功路由并执行。 每个对应的处理器还必须满足 Handler 接口约定。给定 Evidence 类型的 Handler 可以执行任意状态转换, 例如削减质押、监禁以及墓碑标记。

概念

证据

提交到 x/evidence 模块的任何具体证据类型都必须满足 下述 Evidence 约定。并非所有具体证据类型都会以相同方式满足 该约定,而且某些数据对于某些证据类型可能完全无关。 另外还创建了扩展 Evidence 的 ValidatorEvidence, 用于定义针对恶意验证者证据的约定。
// Evidence defines the contract which concrete evidence types of misbehavior
// must implement.
type Evidence interface {
    proto.Message

	Route()

string
	String()

string
	Hash() []byte
	ValidateBasic()

error

	// Height at which the infraction occurred
	GetHeight()

int64
}

// ValidatorEvidence extends Evidence interface to define contract
// for evidence against malicious validators
type ValidatorEvidence interface {
    Evidence

	// The consensus address of the malicious validator at time of infraction
	GetConsensusAddress()

sdk.ConsAddress

	// The total power of the malicious validator at time of infraction
	GetValidatorPower()

int64

	// The total validator set power at time of infraction
	GetTotalPower()

int64
}

注册与处理

x/evidence 模块必须首先了解它预期要处理的所有证据类型。 这是通过将 Evidence 约定中的 Route 方法注册到一个称为 Router 的组件上来实现的(定义如下)。Router 接收 Evidence,并通过 Route 方法为该 Evidence 尝试找到对应的 Handler。
type Router interface {
    AddRoute(r string, h Handler)

Router
  HasRoute(r string)

bool
  GetRoute(path string)

Handler
  Seal()

Sealed()

bool
}
Handler(定义如下)负责执行处理 Evidence 的全部 业务逻辑。这通常包括对证据进行校验, 既包括通过 ValidateBasic 的无状态校验,也包括借助提供给 Handler 的任意 keeper 进行有状态校验。 此外,Handler 还可以执行诸如削减质押和监禁验证者等能力。所有由 Handler 处理的 Evidence 都应被持久化。
// Handler defines an agnostic Evidence handler. The handler is responsible
// for executing all corresponding business logic necessary for verifying the
// evidence as valid. In addition, the Handler may execute any necessary
// slashing and potential jailing.
type Handler func(context.Context, Evidence)

error

状态

当前,x/evidence 模块只会在状态中存储已提交且有效的 Evidence。 证据状态也会存储并导出到 x/evidence 模块的 GenesisState 中。
// GenesisState defines the evidence module's genesis state.
message GenesisState {
  // evidence defines all the evidence at genesis.
  repeated google.protobuf.Any evidence = 1;
}

所有 Evidence 都通过前缀为 0x00(KeyPrefixEvidence)的前缀 KVStore 进行读取和存储。

消息

MsgSubmitEvidence

证据通过 MsgSubmitEvidence 消息提交:
// MsgSubmitEvidence represents a message that supports submitting arbitrary
// Evidence of misbehavior such as equivocation or counterfactual signing.
message MsgSubmitEvidence {
  string              submitter = 1;
  google.protobuf.Any evidence  = 2;
}
注意,MsgSubmitEvidence 消息中的 Evidence 必须在 x/evidence 模块的 Router 中注册有对应的 Handler,才能被正确处理和路由。 如果该 Evidence 已注册对应的 Handler,其处理流程如下:
func SubmitEvidence(ctx Context, evidence Evidence)

error {
    if _, err := GetEvidence(ctx, evidence.Hash()); err == nil {
    return errorsmod.Wrap(types.ErrEvidenceExists, strings.ToUpper(hex.EncodeToString(evidence.Hash())))
}
    if !router.HasRoute(evidence.Route()) {
    return errorsmod.Wrap(types.ErrNoEvidenceHandlerExists, evidence.Route())
}
    handler := router.GetRoute(evidence.Route())
    if err := handler(ctx, evidence); err != nil {
    return errorsmod.Wrap(types.ErrInvalidEvidence, err.Error())
}

ctx.EventManager().EmitEvent(
		sdk.NewEvent(
			types.EventTypeSubmitEvidence,
			sdk.NewAttribute(types.AttributeKeyEvidenceHash, strings.ToUpper(hex.EncodeToString(evidence.Hash()))),
		),
	)

SetEvidence(ctx, evidence)

return nil
}
首先,不能已经存在完全相同类型且已有效提交的 Evidence。 其次,Evidence 会被路由到对应的 Handler 并执行。最后, 如果处理 Evidence 时没有发生错误,则会发出一个事件,并将其持久化到状态中。

事件

x/evidence 模块会发出以下事件:

处理器

MsgSubmitEvidence

类型属性键属性值
submit_evidenceevidence_hash{evidenceHash}
messagemoduleevidence
messagesender{senderAddress}
messageactionsubmit_evidence

参数

证据模块不包含任何参数。

BeginBlock

证据处理

CometBFT 区块可以包含 Evidence, 用于表明某个验证者是否实施了恶意行为。相关信息会作为 abci.RequestBeginBlock 中的 ABCI Evidence 转发给应用,以便据此惩罚该验证者。

双重签名

Cosmos SDK 会在 ABCI BeginBlock 中处理两类证据:
  • DuplicateVoteEvidence
  • LightClientAttackEvidence
证据模块以相同方式处理这两类证据。首先,Cosmos SDK 会将 CometBFT 的具体证据类型转换为 SDK Evidence 接口,并使用 Equivocation 作为具体类型。
// Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.50.x/proto/cosmos/evidence/v1beta1/evidence.proto#L12-L32
某个在 block 中提交的 Equivocation 若要有效,必须满足: Evidence.Timestamp >= block.Timestamp - MaxEvidenceAge 其中:
  • Evidence.Timestamp 是高度为 Evidence.Height 的区块中的时间戳
  • block.Timestamp 是当前区块时间戳。
如果区块中包含有效的 Equivocation 证据,则验证者的质押会按违规发生时的质押数量, 依据 x/slashing 模块中定义的 SlashFractionDoubleSign 进行削减, 而不是按发现证据时的质押数量计算。 我们希望“跟随质押”,也就是说,导致此次违规的那部分质押 即使之后已被重新委托或开始解除绑定,也应被削减。 此外,该验证者会被永久监禁并打上墓碑标记,从而确保该 验证者永远无法重新进入验证者集合。 Equivocation 证据的处理方式如下:
// Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.50.x/x/evidence/keeper/infraction.go#L26-L140
注意: 削减质押、监禁和墓碑标记调用会通过 x/slashing 模块委托执行, 该模块会发出说明性事件,并最终将调用委托给 x/staking 模块。参见 状态转换 中关于削减质押与监禁的文档。

客户端

CLI

用户可以使用 CLI 查询并与 evidence 模块交互。

Query

query 命令允许用户查询 evidence 状态。
simd query evidence --help

evidence

evidence 命令允许用户列出所有证据,或按哈希查询证据。 用法:
simd query evidence evidence [flags]
按哈希查询证据 示例:
simd query evidence evidence "DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"
示例输出:
evidence:
  consensus_address: cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h
  height: 11
  power: 100
  time: "2021-10-20T16:08:38.194017624Z"
获取所有证据 示例:
simd query evidence list
示例输出:
evidence:
  consensus_address: cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h
  height: 11
  power: 100
  time: "2021-10-20T16:08:38.194017624Z"
pagination:
  next_key: null
  total: "1"

REST

用户可以使用 REST 端点查询 evidence 模块。

Evidence

按哈希获取证据
/cosmos/evidence/v1beta1/evidence/{hash}
示例:
curl -X GET "http://localhost:1317/cosmos/evidence/v1beta1/evidence/DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"
示例输出:
{
  "evidence": {
    "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
    "height": "11",
    "power": "100",
    "time": "2021-10-20T16:08:38.194017624Z"
  }
}

All evidence

获取所有证据
/cosmos/evidence/v1beta1/evidence
示例:
curl -X GET "http://localhost:1317/cosmos/evidence/v1beta1/evidence"
示例输出:
{
  "evidence": [
    {
      "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
      "height": "11",
      "power": "100",
      "time": "2021-10-20T16:08:38.194017624Z"
    }
  ],
  "pagination": {
    "total": "1"
  }
}

gRPC

用户可以使用 gRPC 端点查询 evidence 模块。

Evidence

按哈希获取证据
cosmos.evidence.v1beta1.Query/Evidence
示例:
grpcurl -plaintext -d '{"evidence_hash":"DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"}' localhost:9090 cosmos.evidence.v1beta1.Query/Evidence
示例输出:
{
  "evidence": {
    "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
    "height": "11",
    "power": "100",
    "time": "2021-10-20T16:08:38.194017624Z"
  }
}

All evidence

获取所有证据
cosmos.evidence.v1beta1.Query/AllEvidence
示例:
grpcurl -plaintext localhost:9090 cosmos.evidence.v1beta1.Query/AllEvidence
示例输出:
{
  "evidence": [
    {
      "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
      "height": "11",
      "power": "100",
      "time": "2021-10-20T16:08:38.194017624Z"
    }
  ],
  "pagination": {
    "total": "1"
  }
}

Abstract

x/evidence is an implementation of a Cosmos SDK module, per ADR 009, that allows for the submission and handling of arbitrary evidence of misbehavior such as equivocation and counterfactual signing. The evidence module differs from standard evidence handling which typically expects the underlying consensus engine, e.g. CometBFT, to automatically submit evidence when it is discovered by allowing clients and foreign chains to submit more complex evidence directly. All concrete evidence types must implement the Evidence interface contract. Submitted Evidence is first routed through the evidence module’s Router in which 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 in order 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 x/evidence module must fulfill the Evidence contract outlined below. Not all concrete types of evidence will fulfill this contract in the same way and some data may 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.
// Evidence defines the contract which concrete evidence types of misbehavior
// must implement.
type Evidence interface {
    proto.Message

	Route()

string
	String()

string
	Hash() []byte
	ValidateBasic()

error

	// Height at which the infraction occurred
	GetHeight()

int64
}

// ValidatorEvidence extends Evidence interface to define contract
// for evidence against malicious validators
type ValidatorEvidence interface {
    Evidence

	// The consensus address of the malicious validator at time of infraction
	GetConsensusAddress()

sdk.ConsAddress

	// The total power of the malicious validator at time of infraction
	GetValidatorPower()

int64

	// The total validator set power at time of infraction
	GetTotalPower()

int64
}

Registration & Handling

The x/evidence module must first know about all types of evidence it is expected to handle. This is accomplished by registering the Route method in the Evidence contract with what is known as a Router (defined below). The Router accepts Evidence and attempts to find the corresponding Handler for the Evidence via the Route method.
type Router interface {
    AddRoute(r string, h Handler)

Router
  HasRoute(r string)

bool
  GetRoute(path string)

Handler
  Seal()

Sealed()

bool
}
The Handler (defined below) is responsible for executing the entirety of the business logic for handling Evidence. This typically includes validating the evidence, both stateless checks via ValidateBasic and stateful checks via any keepers provided to the Handler. In addition, the Handler may also perform capabilities such as slashing and jailing a validator. All Evidence handled by the Handler should be persisted.
// Handler defines an agnostic Evidence handler. The handler is responsible
// for executing all corresponding business logic necessary for verifying the
// evidence as valid. In addition, the Handler may execute any necessary
// slashing and potential jailing.
type Handler func(context.Context, Evidence)

error

State

Currently the x/evidence module only stores valid submitted Evidence in state. The evidence state is also stored and exported in the x/evidence module’s GenesisState.
// GenesisState defines the evidence module's genesis state.
message GenesisState {
  // evidence defines all the evidence at genesis.
  repeated google.protobuf.Any evidence = 1;
}

All Evidence is retrieved and stored via a prefix KVStore using prefix 0x00 (KeyPrefixEvidence).

Messages

MsgSubmitEvidence

Evidence is submitted through a MsgSubmitEvidence message:
// MsgSubmitEvidence represents a message that supports submitting arbitrary
// Evidence of misbehavior such as equivocation or counterfactual signing.
message MsgSubmitEvidence {
  string              submitter = 1;
  google.protobuf.Any evidence  = 2;
}
Note, the Evidence of a MsgSubmitEvidence message must have a corresponding Handler registered with the x/evidence module’s Router in order to be processed and routed correctly. Given the Evidence is registered with a corresponding Handler, it is processed as follows:
func SubmitEvidence(ctx Context, evidence Evidence)

error {
    if _, err := GetEvidence(ctx, evidence.Hash()); err == nil {
    return errorsmod.Wrap(types.ErrEvidenceExists, strings.ToUpper(hex.EncodeToString(evidence.Hash())))
}
    if !router.HasRoute(evidence.Route()) {
    return errorsmod.Wrap(types.ErrNoEvidenceHandlerExists, evidence.Route())
}
    handler := router.GetRoute(evidence.Route())
    if err := handler(ctx, evidence); err != nil {
    return errorsmod.Wrap(types.ErrInvalidEvidence, err.Error())
}

ctx.EventManager().EmitEvent(
		sdk.NewEvent(
			types.EventTypeSubmitEvidence,
			sdk.NewAttribute(types.AttributeKeyEvidenceHash, strings.ToUpper(hex.EncodeToString(evidence.Hash()))),
		),
	)

SetEvidence(ctx, evidence)

return nil
}
First, there must not already exist valid submitted Evidence of the exact same type. Secondly, the Evidence is routed to the Handler and executed. Finally, if there is no error in handling the Evidence, an event is emitted and it is persisted to state.

Events

The x/evidence module emits the following events:

Handlers

MsgSubmitEvidence

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

Parameters

The evidence module does not contain any parameters.

BeginBlock

Evidence Handling

CometBFT blocks can include Evidence that indicates if a validator committed malicious behavior. The relevant information is forwarded to the application as ABCI Evidence in abci.RequestBeginBlock so that the validator can be punished accordingly.

Equivocation

The Cosmos 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 Cosmos SDK converts the CometBFT concrete evidence type to an SDK Evidence interface using Equivocation as the concrete type.
// Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.50.x/proto/cosmos/evidence/v1beta1/evidence.proto#L12-L32
For some Equivocation submitted in block to be valid, it must satisfy: 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 (slashed) by SlashFractionDoubleSign as defined by the x/slashing module of what their stake was when the infraction occurred, rather than when the evidence was discovered. We want to “follow the stake”, i.e., the stake that contributed to the infraction should be slashed, even if it has since been redelegated or started unbonding. In addition, the validator is permanently jailed and tombstoned to make it impossible for that validator to ever re-enter the validator set. The Equivocation evidence is handled as follows:
// Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.50.x/x/evidence/keeper/infraction.go#L26-L140
Note: The slashing, jailing, and tombstoning calls are delegated through the x/slashing module that emits informative events and finally delegates calls to the x/staking module. See documentation on slashing and jailing in State Transitions.

Client

CLI

A user can query and interact with the evidence module using the CLI.

Query

The query command allows users to query evidence state.
simd query evidence --help

evidence

The evidence command allows users to list all evidence or evidence by hash. Usage:
simd query evidence evidence [flags]
To query evidence by hash Example:
simd query evidence evidence "DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"
Example Output:
evidence:
  consensus_address: cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h
  height: 11
  power: 100
  time: "2021-10-20T16:08:38.194017624Z"
To get all evidence Example:
simd query evidence list
Example Output:
evidence:
  consensus_address: cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h
  height: 11
  power: 100
  time: "2021-10-20T16:08:38.194017624Z"
pagination:
  next_key: null
  total: "1"

REST

A user can query the evidence module using REST endpoints.

Evidence

Get evidence by hash
/cosmos/evidence/v1beta1/evidence/{hash}
Example:
curl -X GET "http://localhost:1317/cosmos/evidence/v1beta1/evidence/DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"
Example Output:
{
  "evidence": {
    "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
    "height": "11",
    "power": "100",
    "time": "2021-10-20T16:08:38.194017624Z"
  }
}

All evidence

Get all evidence
/cosmos/evidence/v1beta1/evidence
Example:
curl -X GET "http://localhost:1317/cosmos/evidence/v1beta1/evidence"
Example Output:
{
  "evidence": [
    {
      "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
      "height": "11",
      "power": "100",
      "time": "2021-10-20T16:08:38.194017624Z"
    }
  ],
  "pagination": {
    "total": "1"
  }
}

gRPC

A user can query the evidence module using gRPC endpoints.

Evidence

Get evidence by hash
cosmos.evidence.v1beta1.Query/Evidence
Example:
grpcurl -plaintext -d '{"evidence_hash":"DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"}' localhost:9090 cosmos.evidence.v1beta1.Query/Evidence
Example Output:
{
  "evidence": {
    "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
    "height": "11",
    "power": "100",
    "time": "2021-10-20T16:08:38.194017624Z"
  }
}

All evidence

Get all evidence
cosmos.evidence.v1beta1.Query/AllEvidence
Example:
grpcurl -plaintext localhost:9090 cosmos.evidence.v1beta1.Query/AllEvidence
Example Output:
{
  "evidence": [
    {
      "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h",
      "height": "11",
      "power": "100",
      "time": "2021-10-20T16:08:38.194017624Z"
    }
  ],
  "pagination": {
    "total": "1"
  }
}