惩罚(Slashing)
Last updated
Last updated
本节指定了 Cosmos SDK 的削减模块,该模块实现了 2016 年 6 月 中首次概述的功能。
削减模块使基于 Cosmos SDK 的区块链能够通过惩罚(“削减”)来遏制协议认可的、拥有价值抵押的参与者的任何可归责行为。
惩罚可能包括但不限于:
销毁其部分抵押资产
在一段时间内剥夺其对未来区块的投票能力
该模块将被 Cosmos Hub 使用,Cosmos Hub 是 Cosmos 生态系统中的第一个中心。
在任何给定时间,状态机中都注册有若干验证者。每个区块,由 x/staking
定义的 MaxValidators
中排名靠前且未被监禁的验证者将成为绑定状态,这意味着他们可以提议并投票表决区块。处于绑定状态的验证者拥有抵押资产,这意味着如果他们犯下协议错误,他们及其委托者的部分或全部抵押资产将面临风险。
对于这些验证者中的每一个,我们都保留一个 ValidatorSigningInfo
记录,其中包含与验证者的活跃性及其他与违规相关的属性信息。
为了减轻最初可能出现的非恶意协议错误的类别所带来的影响,Cosmos Hub 为每个验证者实现了一个墓碑上限,该上限仅允许验证者因双重签名错误被削减一次。例如,如果您错误配置了 HSM 并对许多旧区块进行了双重签名,您只会因第一次双重签名受到惩罚(随后立即被墓碑化)。这仍然会非常昂贵且应尽量避免,但墓碑上限在一定程度上减缓了无意错误配置所带来的经济影响。
活跃性错误没有上限,因为它们无法相互叠加。活跃性问题在违规发生时即被“检测”到,验证者会立即被监禁,因此在未解除监禁的情况下,他们不可能犯下多个活跃性错误。
为了说明 x/slashing
模块如何通过 CometBFT
共识处理提交的证据,请参考以下示例:
定义:
[ : 时间线开始
] : 时间线结束
Cn : 第 n
次违规发生
Dn : 第 n
次违规被发现
Vb : 验证人已绑定 (bonded
)
Vu : 验证人已解绑 (unbonded
)
[----------C1----D1,Vu-----]
发生一次违规,随后被发现,此时验证人被解绑,并因该违规而被全额惩罚。
[----------C1--C2---C3---D1,D2,D3Vu-----]
发生多次违规,随后被发现,此时验证人被监禁,并仅因一次违规而被惩罚。由于验证人也被设为“墓碑”状态(tombstoned),他们无法重新加入验证人集。
如果验证人未能在一定数量的区块中包含在 LastCommitInfo
中,他们将被自动监禁,可能会被惩罚并解绑。
验证人存活活动的信息通过 ValidatorSigningInfo
进行跟踪。它在存储中的索引如下:
ValidatorSigningInfo
:0x01 | ConsAddrLen (1 字节) | ConsAddress -> ProtocolBuffer(ValSigningInfo)
MissedBlocksBitArray
:0x02 | ConsAddrLen (1 字节) | ConsAddress | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)
(varint
是一种数字编码格式)
第一个映射允许我们基于验证人的共识地址轻松查找验证人的最近签名信息。
第二个映射 (MissedBlocksBitArray
) 充当一个大小为 SignedBlocksWindow
的位数组,告诉我们验证人是否在给定索引的位数组中错过了区块。位数组中的索引是小端格式的 uint64
。结果是一个 varint
,其值为 0 或 1,其中 0 表示验证人未错过(已签名)对应的区块,1 表示他们错过了该区块(未签名)。
请注意,MissedBlocksBitArray
并未显式初始化。随着新绑定验证人通过第一个 SignedBlocksWindow
区块的进展,键会被添加进去。SignedBlocksWindow
参数定义了用于跟踪验证人存活性的滑动窗口的大小(区块数)。
用于跟踪验证人存活性的信息如下:
x/slashing
模块将其参数存储在状态中,前缀为 0x00
,这些参数可以通过治理或具有权限的地址进行更新。
Params: 0x00 | ProtocolBuffer(Params)
在本节中,我们描述了 x/slashing
模块的消息处理过程。
如果一个验证人由于宕机被自动解绑并希望重新上线并可能重新加入绑定集,它必须发送 MsgUnjail
:
以下是 MsgSrv/Unjail
RPC 的伪代码:
如果验证人的质押足够多,能够进入前 n = MaximumBondedValidators
名,它将自动重新绑定,所有仍然委托给该验证人的委托人也将重新绑定,并开始再次收集配给和奖励。
在每个区块的开始,我们更新每个验证人的 ValidatorSigningInfo
并检查他们是否在滑动窗口内低于存活性阈值。这个滑动窗口由 SignedBlocksWindow
定义,窗口中的索引由验证人 ValidatorSigningInfo
中的 IndexOffset
确定。对于每个处理的区块,无论验证人是否签名,IndexOffset
都会递增。确定索引后,MissedBlocksBitArray
和 MissedBlocksCounter
会相应更新。
最后,为了确定验证人是否低于存活性阈值,我们获取错过的最大区块数 maxMissed
,它是 SignedBlocksWindow - (MinSignedPerWindow * SignedBlocksWindow)
,以及可以确定存活性的最小高度 minHeight
。如果当前区块高度大于 minHeight
且验证人的 MissedBlocksCounter
大于 maxMissed
,则他们将被惩罚 SlashFractionDowntime
,被监禁 DowntimeJailDuration
,并且以下值将被重置:MissedBlocksBitArray
、MissedBlocksCounter
和 IndexOffset
。
注意:存活性惩罚不会导致“墓碑”状态。
本节包含模块钩子的描述。钩子是当事件触发时自动执行的操作。
x/slashing
模块实现了在 x/staking
中定义的 StakingHooks
,用于记录验证人信息。在应用初始化期间,这些钩子应当在质押模块结构体中注册。
以下钩子会影响惩罚状态:
AfterValidatorBonded:创建一个 ValidatorSigningInfo
实例,如以下节所述。
AfterValidatorCreated:存储验证人的共识密钥。
AfterValidatorRemoved:移除验证人的共识密钥。
当新验证人首次成功绑定时,我们为当前已绑定的验证人创建一个新的 ValidatorSigningInfo
结构,并将当前区块的 StartHeight
作为其高度。如果验证人之前已退出验证人集并重新绑定,则其新的绑定高度会被设置。
x/slashing
模块会触发以下事件:
message
module
slashing
message
sender
{validatorAddress}
slash
address
{validatorConsensusAddress}
slash
power
{validatorPower}
slash
reason
{slashReason}
slash
jailed [0]
{validatorConsensusAddress}
slash
burned coins
{math.Int}
[0] 仅在验证人被监禁时包含。
liveness
address
{validatorConsensusAddress}
liveness
missed_blocks
{missedBlocksCounter}
liveness
height
{blockHeight}
与来自 HandleValidatorSignature
的 "slash" 事件相同,但没有 jailed
属性。
slash
jailed
{validatorAddress}
在当前 x/slashing
模块的实现中,当共识引擎通知状态机某个验证人发生共识故障时,该验证人会被部分惩罚,并进入 监禁期(jail period),在此期间无法重新加入验证人集。然而,由于共识故障和 ABCI 的特性,从违规发生到证据到达状态机之间可能存在延迟(这也是 解绑期(unbonding period) 存在的主要原因之一)。
Note: 墓碑(tombstone) 概念仅适用于违规发生与证据到达状态机之间存在延迟的故障。例如,验证人的 双重签名 证据可能由于证据传播层的不确定性而延迟到达状态机,并且验证人可能选择性地披露其双重签名(例如,仅向不常在线的轻客户端披露)。另一方面,存活性违规 会在违规发生时立即被检测到,因此不需要 惩罚期(slashing period),验证人会立即进入 监禁期,并且在 解除监禁(unjail) 之前无法再次触发存活性违规。未来,可能会有其他 拜占庭故障 也存在延迟(例如,将无效提案的证据作为交易提交)。如果实现此类检测机制,需要决定这些故障是否会导致 墓碑状态(如果不会,则它们的惩罚金额不会受 惩罚期 限制)。
在当前系统设计下,当验证人因共识故障进入 监禁期 后,在 监禁期 结束后可以发送交易解除监禁,从而重新加入验证人集。
x/slashing
模块的一个 设计目标 是:如果在执行证据(即验证人进入 监禁期)之前发生了多个违规,验证人只应被惩罚最严重的一次,而不是累计计算。例如:
验证人 A 发生违规 1(罚款 30%)
验证人 A 发生违规 2(罚款 40%)
验证人 A 发生违规 3(罚款 35%)
违规 1 的证据到达状态机(验证人 A 进入 监禁期)
违规 2 的证据到达状态机
违规 3 的证据到达状态机
最终,只有 违规 2 产生的罚款生效(即 40%),因为它是最大值。这种设计保证了在验证人的 共识密钥 被攻击时,即使攻击者进行了多次双重签名,验证人也只会受到一次惩罚。由于解除监禁需要 验证人操作密钥 执行交易,验证人有机会重新保护其 共识密钥,并通过 操作密钥 表明已恢复安全状态。此期间的最大违规被称为 惩罚期(slashing period)。
当验证人解除监禁并重新加入验证人集后,会开始一个新的 惩罚期;如果他们在 解除监禁后 发生新的违规,则该违规会 累加 到上一个 惩罚期 的最大惩罚之上。
然而,由于证据提交可以在 解绑期 内进行,即使进入新的 惩罚期,仍然需要允许针对 前一个惩罚期 的证据提交。例如:
验证人 A 发生违规 1(罚款 30%)
验证人 A 发生违规 2(罚款 40%)
违规 1 的证据到达状态机(验证人 A 进入 监禁期)
验证人 A 解除监禁(进入新的 惩罚期)
尽管进入了新的 惩罚期,但仍需要允许提交 违规 2 的证据,因为它可能在 解绑期 内仍然有效。随着 惩罚期 的增加,系统需要跟踪每个 惩罚期 内的最大违规,这会导致更高的复杂性。
最大惩罚周期数量为 len(UnbondingPeriod) / len(JailPeriod)
。当前 Gaia 的默认 UnbondingPeriod
和 JailPeriod
分别为 3 周和 2 天。这意味着,每个验证人最多可能同时跟踪 11 个惩罚周期。如果设置 JailPeriod >= UnbondingPeriod
,则只需跟踪 1 个惩罚周期(即不需要跟踪多个惩罚周期)。
当前的 JailPeriod
实现中,一旦验证人解除监禁,所有委托给该验证人的委托者(尚未解除绑定或重新委托)仍然保留其委托关系。由于共识安全故障的严重性远高于活跃性故障,可能更为谨慎的做法是让委托者在验证人解除监禁后不会自动重新绑定。
我们提议,对于发生共识安全故障的验证人,将其监禁时间设为无限期(即墓碑状态)。这将使验证人被永久移出验证人集合,并且不允许重新进入。所有委托者(包括验证人自身)必须选择解除绑定或重新委托到其他验证人。验证人运营者如果希望继续运营,可以创建一个新的验证人,但必须使用新的运营者密钥和共识密钥,并重新赢得委托。
实施墓碑系统并移除惩罚周期跟踪,将使 Slashing
模块大幅简化,尤其是因为可以移除 Slashing
模块中由 Staking
模块使用的所有钩子(但 Slashing
模块仍然会使用 Staking
定义的钩子)。
另一种优化方式是,如果假设 CometBFT 共识的所有 ABCI 故障都在相同级别上进行惩罚,则无需跟踪“最大惩罚金额”。一旦发生 ABCI 故障,就不需要比较未来可能发生的故障来确定最大惩罚金额。
当前唯一的 CometBFT ABCI 故障是:
无正当理由的预提交(双重签名)
计划在不久的将来新增以下故障:
在解除绑定阶段签署预提交(此机制用于确保轻客户端的二分安全性)
由于这些故障都属于可归因的拜占庭故障,我们可能希望对它们实施相同的惩罚,因此可以进行上述更改。
Note: 该更改适用于当前的 CometBFT 共识,但对于其他共识算法或未来版本的 CometBFT(可能引入不同级别的惩罚,例如部分惩罚)可能并不适用。
Slashing
模块包含以下参数:
SignedBlocksWindow
string (int64)
"100"
MinSignedPerWindow
string (dec)
"0.500000000000000000"
DowntimeJailDuration
string (ns)
"600000000000"
SlashFractionDoubleSign
string (dec)
"0.050000000000000000"
SlashFractionDowntime
string (dec)
"0.010000000000000000"
用户可以使用 CLI 查询和交互 Slashing
模块。
查询命令允许用户查询 Slashing
状态。
params
命令允许用户查询 Slashing
模块的创世参数。
示例:
示例输出:
signing-info
命令允许用户使用共识公钥查询验证人的签名信息。
示例:
示例输出:
signing-infos
命令允许用户查询所有验证人的签名信息。
示例:
示例输出:
tx
命令允许用户与 Slashing
模块进行交互。
unjail
命令允许用户解除因停机而被监禁的验证人的监禁。
示例:
用户可以使用 gRPC 端点查询 Slashing
模块。
Params
端点允许用户查询 Slashing
模块的参数。
示例:
示例输出:
SigningInfo
查询给定共识地址的签名信息。
示例:
示例输出:
SigningInfos
查询所有验证人的签名信息。
示例:
示例输出:
用户可以使用 REST 端点查询 Slashing
模块。
示例:
示例输出
示例:
示例输出:
示例:
示例输出:
每个区块都包含验证人对前一个区块的预提交集,这些预提交集被称为由 CometBFT
提供的 LastCommitInfo
。只要 LastCommitInfo
包含来自总投票权 +2/3 的预提交,它就是有效的。
提议者通过在 CometBFT
的 LastCommitInfo
中包含所有验证人的预提交来获得激励,激励为与 LastCommitInfo
中包含的投票权与 +2/3 之间的差异成正比的额外费用(参见)。
Note:目前,根据 x/slashing
模块的规范,每次验证人 解绑 并重新 绑定 时,都会创建一个新的 惩罚期。但应该改为 监禁/解除监禁 触发新的 惩罚期,详情见 issue 。