变更记录

  • 2019 年 7 月 31 日:初始草案
  • 2019 年 10 月 24 日:初始实现

状态

已接受

背景

为了支持构建高安全性、健壮且具备互操作性的区块链应用,Cosmos SDK 必须提供一种机制,使任意证据都能够被提交、评估和验证,并对验证者实施的任何不当行为施加某种约定好的惩罚,例如重复签名(双重投票)、在已解绑状态下签名、对错误的状态转换进行签名(未来可能支持)等。 此外,这种机制对于任何 IBC 或跨链验证协议的实现也至关重要,因为它支持将任何不当行为从抵押链中继回主链,从而对发生双重签名的验证者进行罚没。

决策

我们将在 Cosmos SDK 中实现一个证据模块,支持以下功能:
  • 为开发者提供必要的抽象和接口,用于定义自定义证据消息、消息处理器,以及针对不当行为执行罚没和惩罚的方法。
  • 支持将证据消息路由到任意模块中的处理器,以判定所提交不当行为的有效性。
  • 支持通过治理修改任意证据类型对应的罚没惩罚参数。
  • 实现查询器,以支持查询参数、证据类型、参数,以及所有已提交且有效的不当行为。

类型

首先,我们定义 Evidence 接口类型。x/evidence 模块可以实现其自身类型,供多条链复用(例如 CounterFactualEvidence)。 此外,其他模块也可以采用类似于治理可扩展的方式,实现各自的 Evidence 类型。需要注意的是,任何实现 Evidence 接口的具体类型都可以包含任意字段,例如违规时间。我们希望 Evidence 类型尽可能保持灵活。 向 x/evidence 模块提交证据时,具体类型必须提供验证者的共识地址,该地址应为 x/slashing 模块所知(假设该违规有效),还必须提供违规发生的高度,以及该高度下验证者的投票权重。
type Evidence interface {
    Route()

string
  Type()

string
  String()

string
  Hash()

HexBytes
  ValidateBasic()

error

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

ConsAddress

  // Height at which the infraction occurred
  GetHeight()

int64

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

int64

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

int64
}

路由与处理

每种 Evidence 类型都必须映射到一个唯一的特定路由,并注册到 x/evidence 模块中。这通过 Router 实现完成。
type Router interface {
    AddRoute(r string, h Handler)

Router
  HasRoute(r string)

bool
  GetRoute(path string)

Handler
  Seal()
}
在通过 x/evidence 模块成功路由后,Evidence 类型会被传递给一个 Handler。该 Handler 负责执行所有必要的对应业务逻辑,以验证该证据是否有效。 此外,Handler 还可以执行任何必要的罚没和监禁操作。由于罚没比例通常会来自某种静态函数,允许 Handler 执行这些操作能够提供最大的灵活性。一个示例可能是 k * evidence.GetValidatorPower(),其中 k 是由治理控制的链上参数。Evidence 类型应当提供所有必要的外部信息,以便 Handler 做出所需的状态转换。 如果未返回错误,则该 Evidence 被视为有效。
type Handler func(Context, Evidence)

error

提交

Evidence 通过 MsgSubmitEvidence 消息类型提交,该消息在内部由 x/evidence 模块的 SubmitEvidence 处理。
type MsgSubmitEvidence struct {
    Evidence
}

func handleMsgSubmitEvidence(ctx Context, keeper Keeper, msg MsgSubmitEvidence)

Result {
    if err := keeper.SubmitEvidence(ctx, msg.Evidence); err != nil {
    return err.Result()
}

  // emit events...

  return Result{
    // ...
}
}
x/evidence 模块的 keeper 负责将 Evidence 与模块路由器进行匹配,并调用相应的 Handler,其中可能包括对验证者执行罚没和监禁。成功后,所提交的证据会被持久化保存。
func (k Keeper)

SubmitEvidence(ctx Context, evidence Evidence)

error {
    handler := keeper.router.GetRoute(evidence.Route())
    if err := handler(ctx, evidence); err != nil {
    return ErrInvalidEvidence(keeper.codespace, err)
}

keeper.setEvidence(ctx, evidence)

return nil
}

创世状态

最后,我们需要表示 x/evidence 模块的创世状态。该模块只需要所有已提交且有效的违规记录列表,以及处理提交证据所需的必要参数。x/evidence 模块会自然地定义并路由原生证据类型,而这些类型很可能需要对应的罚没惩罚常量。
type GenesisState struct {
    Params       Params
  Infractions  []Evidence
}

影响

正面

  • 允许状态机处理链上提交的不当行为,并根据约定好的罚没参数对验证者实施惩罚。
  • 允许任意模块定义和处理证据类型。这进一步允许通过更复杂的机制来定义罚没和监禁。
  • 不再完全依赖 Tendermint 提交证据。

负面

  • 由于无法引入新证据类型对应的处理器,因此在运行中的链上无法通过治理轻松引入新的证据类型。

中性

  • 我们是否应当无限期持久化保存违规记录?还是应该改为依赖事件?

参考资料


Changelog

  • 2019 July 31: Initial draft
  • 2019 October 24: Initial implementation

Status

Accepted

Context

In order to support building highly secure, robust and interoperable blockchain applications, it is vital for the Cosmos SDK to expose a mechanism in which arbitrary evidence can be submitted, evaluated and verified resulting in some agreed upon penalty for any misbehavior committed by a validator, such as equivocation (double-voting), signing when unbonded, signing an incorrect state transition (in the future), etc. Furthermore, such a mechanism is paramount for any IBC or cross-chain validation protocol implementation in order to support the ability for any misbehavior to be relayed back from a collateralized chain to a primary chain so that the equivocating validator(s) can be slashed.

Decision

We will implement an evidence module in the Cosmos SDK supporting the following functionality:
  • Provide developers with the abstractions and interfaces necessary to define custom evidence messages, message handlers, and methods to slash and penalize accordingly for misbehavior.
  • Support the ability to route evidence messages to handlers in any module to determine the validity of submitted misbehavior.
  • Support the ability, through governance, to modify slashing penalties of any evidence type.
  • Querier implementation to support querying params, evidence types, params, and all submitted valid misbehavior.

Types

First, we define the Evidence interface type. The x/evidence module may implement its own types that can be used by many chains (e.g. CounterFactualEvidence). In addition, other modules may implement their own Evidence types in a similar manner in which governance is extensible. It is important to note any concrete type implementing the Evidence interface may include arbitrary fields such as an infraction time. We want the Evidence type to remain as flexible as possible. When submitting evidence to the x/evidence module, the concrete type must provide the validator’s consensus address, which should be known by the x/slashing module (assuming the infraction is valid), the height at which the infraction occurred and the validator’s power at same height in which the infraction occurred.
type Evidence interface {
    Route()

string
  Type()

string
  String()

string
  Hash()

HexBytes
  ValidateBasic()

error

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

ConsAddress

  // Height at which the infraction occurred
  GetHeight()

int64

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

int64

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

int64
}

Routing & Handling

Each Evidence type must map to a specific unique route and be registered with the x/evidence module. It accomplishes this through the Router implementation.
type Router interface {
    AddRoute(r string, h Handler)

Router
  HasRoute(r string)

bool
  GetRoute(path string)

Handler
  Seal()
}
Upon successful routing through the x/evidence module, the Evidence type is passed through a Handler. This 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. Since slashing fractions will typically result from some form of static functions, allow the Handler to do this provides the greatest flexibility. An example could be k * evidence.GetValidatorPower() where k is an on-chain parameter controlled by governance. The Evidence type should provide all the external information necessary in order for the Handler to make the necessary state transitions. If no error is returned, the Evidence is considered valid.
type Handler func(Context, Evidence)

error

Submission

Evidence is submitted through a MsgSubmitEvidence message type which is internally handled by the x/evidence module’s SubmitEvidence.
type MsgSubmitEvidence struct {
    Evidence
}

func handleMsgSubmitEvidence(ctx Context, keeper Keeper, msg MsgSubmitEvidence)

Result {
    if err := keeper.SubmitEvidence(ctx, msg.Evidence); err != nil {
    return err.Result()
}

  // emit events...

  return Result{
    // ...
}
}
The x/evidence module’s keeper is responsible for matching the Evidence against the module’s router and invoking the corresponding Handler which may include slashing and jailing the validator. Upon success, the submitted evidence is persisted.
func (k Keeper)

SubmitEvidence(ctx Context, evidence Evidence)

error {
    handler := keeper.router.GetRoute(evidence.Route())
    if err := handler(ctx, evidence); err != nil {
    return ErrInvalidEvidence(keeper.codespace, err)
}

keeper.setEvidence(ctx, evidence)

return nil
}

Genesis

Finally, we need to represent the genesis state of the x/evidence module. The module only needs a list of all submitted valid infractions and any necessary params for which the module needs in order to handle submitted evidence. The x/evidence module will naturally define and route native evidence types for which it’ll most likely need slashing penalty constants for.
type GenesisState struct {
    Params       Params
  Infractions  []Evidence
}

Consequences

Positive

  • Allows the state machine to process misbehavior submitted on-chain and penalize validators based on agreed upon slashing parameters.
  • Allows evidence types to be defined and handled by any module. This further allows slashing and jailing to be defined by more complex mechanisms.
  • Does not solely rely on Tendermint to submit evidence.

Negative

  • No easy way to introduce new evidence types through governance on a live chain due to the inability to introduce the new evidence type’s corresponding handler

Neutral

  • Should we persist infractions indefinitely? Or should we rather rely on events?

References