变更记录

  • 2021-02-10:初始草案

作者

  • Dev Ojha (@valardragon)
  • Sunny Aggarwal (@sunnya97)

状态

提议中

摘要

本 ADR 更新了权益证明模块:在更新共识层的质押权重之前,先将质押权重更新缓冲若干个区块。这个缓冲长度称为一个纪元。此前质押模块的功能则是该抽象模块的一个特例,即纪元长度设为 1 个区块。

背景

当前的权益证明模块在设计上选择将质押权重变化立即应用到共识引擎。这意味着委托和解除质押会立即应用到验证者集合。之所以主要这样设计,是因为它在实现上最简单,而且我们当时认为这会为客户端带来更好的用户体验。 另一种设计选择是允许将质押更新(委托、解除质押、验证者加入)缓冲若干个区块。这种按纪元划分的权益证明共识能够保证:除非发生惩罚事件,验证者的共识权重不会在纪元中途发生变化。 此外,用户体验上的障碍可能并不像之前想象的那么显著。这是因为可以立即向用户确认其质押已被记录,并将在后续执行。 进一步地,随着时间推移,我们也更清楚地认识到,立即执行质押事件存在一些限制,例如:
  • 基于阈值的密码学。主要限制之一在于,由于验证者集合变化过于频繁,固定验证者集合难以运行多方计算。区块链中的许多基于阈值的密码学特性,例如随机信标和阈值解密,都需要计算开销很大的 DKG 过程(创建时间会远超 1 个区块)。若要高效使用这些特性,我们需要保证 DKG 的结果能在相当长的一段时间内被使用。每个区块都重新运行 DKG 并不可行。通过引入纪元化质押,可以保证我们只需每个纪元运行一次新的 DKG。
  • 轻客户端效率。当验证者集合发生高频变动时,这将减少 IBC 的开销。在 Tendermint 轻客户端二分算法中,需要验证的头信息数量与受信任头和最新头之间验证者集合差异的上界有关。如果差异过大,就需要验证这两者之间更多的头信息。通过限制验证者集合变化的频率,我们可以减小 IBC 轻客户端证明在最坏情况下的大小,而最坏情况正出现在验证者集合高频变动时。
  • 确定性领导者选举的公平性。目前,在没有纪元的情况下,我们无法对质押变化存在时的确定性领导者选举公平性进行推理(tendermint/spec#217)。破坏领导者选举的公平性对验证者是有利可图的,因为他们会因成为提议者而获得额外奖励。引入纪元至少能让我们的确定性领导者选举更接近于可被证明安全的模型。(尽管如此,我们仍未证明当前算法在存在质押变化且验证者数量 > 2 时是否公平。)
  • 质押衍生品设计。目前,奖励分配通过 F1 费用分配进行惰性处理。虽然这节省了计算复杂度,但惰性记账要求质押实现维护更多状态。现在,每条委托记录都必须跟踪上次提取奖励的时间。对于某些希望让质押给单个验证者的所有代币都具备同质可替代性的质押衍生品设计来说,这会带来挑战。强制向用户提取奖励可以帮助解决这个问题,但不可能按区块强制向用户提取奖励。有了纪元后,链可以更容易将设计改为强制提取奖励(仅需每个纪元遍历一次委托人账户),从而将委托时间信息从状态中移除。这对某些质押衍生品设计可能很有用。

设计考量

惩罚

这里有一个设计考量:惩罚应当立即应用,还是在纪元结束时应用。一次惩罚事件只应作用于违规发生期间实际处于质押状态的成员,也就是惩罚事件发生所在纪元中的成员。 立即应用可以被视为提供了更高的共识层安全性,但可能会对上述用例带来代价。立即惩罚在共识层安全性上的收益,其实都可以通过立即执行验证者监禁(从而将其移出验证者集合),并将真正影响验证者权重的惩罚变更延迟到纪元边界来获得。对于上面提到的用例,也可以集成一些变通方案来避免问题,如下所示:
  • 对于基于阈值的密码学,这种设置会让阈值密码学继续使用原始纪元权重,而共识层则拥有一项更新,使其能更快地从额外安全性中获益。如果基于阈值的密码学阻碍了链的活性,那么在该纪元剩余时间内,我们实际上提高了其余验证者需要满足的活性阈值。(另一种方式是,被监禁的节点仍然可以贡献分片。)在单个纪元内若有超过 1/3 的验证者被监禁,这个方案将在极端情况下失效。对于这种极端场景,链本身通常已经有自定义的事故响应方案,而如何处理阈值密码学也应成为其中的一部分。
  • 对于轻客户端效率,可以在头信息中加入一个比特位,用于指示纪元内惩罚(参见链接)。
  • 对于确定性领导者选举的公平性,在纪元内应用惩罚或监禁会破坏我们原本想提供的保证。这会重新引入一个新的问题(但比原问题简单得多),即如何给出公平性保证。具体来说,验证者可以通过对抗性方式选择将自己移出提议者集合。从安全性角度看,这或许可以通过两种不同机制来处理(或者最终证明依然过于困难)。第一种是给出安全性表述,承认对手能够在一个纪元内强制让一个预先固定阈值数量的参与者退出提议者集合。第二种方法是进行参数化设置,使得纪元内被惩罚的成本远远高于成为提议者所带来的收益。然而,后一条标准相当可疑,因为在具有复杂状态机的链上,成为提议者可能带来许多额外好处。(例如 Fomo3D 之类的 DeFi 博弈)
  • 对于质押衍生品设计,不会引入问题。这不会增加质押记录的状态大小,因为是否发生过惩罚,可以仅凭验证者地址完整查询出来。

代币锁定

当有人发起委托交易时,即使他们不会立即进入质押状态,其代币也应被转入一个由质押模块管理的资金池,并在纪元结束时使用。这样可以避免一种情况:用户完成质押操作后,又花掉这些代币,却没有意识到它们已经被分配用于质押,从而导致其质押交易失败。

纪元流水线化

尤其对于基于阈值的密码学,我们需要为纪元变更建立流水线。这是因为当我们处于纪元 N 时,希望纪元 N+1 的权重已经固定,以便验证者集合据此执行 DKG。因此,如果当前处于纪元 N,那么纪元 N+1 的质押权重应当已经固定,而新的质押变更则应应用到纪元 N + 2。 这可以通过为纪元流水线长度设置一个参数来处理。为降低切换流水线长度所带来的实现复杂度,这个参数除硬分叉期间外不应允许修改。 当流水线长度为 1 时,如果我在纪元 N 期间重新委托,那么我的重新委托会在纪元 N+1 开始前生效。 当流水线长度为 2 时,如果我在纪元 N 期间重新委托,那么我的重新委托会在纪元 N+2 开始前生效。

奖励

尽管所有质押更新都在纪元边界应用,但奖励在被领取时仍然可以立即发放。这是因为它们不会影响当前的质押权重,因为我们没有实现奖励自动质押。如果未来要实现该特性,则必须将其设计为在纪元边界自动质押奖励。

纪元长度参数化

在选择纪元长度时,需要在排队状态/计算积压与缓解前文讨论的即时执行限制(如果这些限制适用于某条链)之间做权衡。 在引入支持可变区块时间的 ABCI 机制之前,不建议使用较长的纪元长度,因为这会导致计算积压。这是因为当某个区块的执行时间超过 Tendermint 预期的出块时间时,轮次可能会增加。

决策

步骤 1:为所有质押与惩罚消息实现缓冲。 首先,我们创建一个资金池,用于存储正在绑定、但应在纪元边界应用的代币,称为 EpochDelegationPool。然后,我们设置两个独立队列:一个用于质押,一个用于惩罚。下面说明各类消息送达时的处理方式:

质押消息

  • MsgCreateValidator:立即将用户的自绑定转入 EpochDelegationPool。将一条消息加入队列,在纪元边界处理该自绑定,并从 EpochDelegationPool 中提取资金。如果纪元执行失败,则将资金从 EpochDelegationPool 退回到用户账户。
  • MsgEditValidator:校验消息;若有效,则将该消息加入队列,在纪元结束时执行。
  • MsgDelegate:立即将用户资金转入 EpochDelegationPool。将一条消息加入队列,在纪元边界处理该委托,并从 EpochDelegationPool 中提取资金。如果纪元执行失败,则将资金从 EpochDelegationPool 退回到用户账户。
  • MsgBeginRedelegate:校验消息;若有效,则将该消息加入队列,在纪元结束时执行。
  • MsgUndelegate:校验消息;若有效,则将该消息加入队列,在纪元结束时执行。

惩罚消息

  • MsgUnjail:校验消息;若有效,则将该消息加入队列,在纪元结束时执行。
  • Slash Event:每当创建一个惩罚事件时,就将其加入惩罚模块中的队列,并在纪元结束时应用。队列应设计为该惩罚会立即生效。

证据消息

  • MsgSubmitEvidence:该消息会立即执行,验证者也会立即被监禁。不过在惩罚性削减中,实际的削减事件会进入队列。
然后我们在 EndBlocker 中添加方法,以确保在 epoch 边界处清空队列并应用委托更新。 步骤 2:实现对排队中的质押交易的查询。 在查询某个地址的质押活动时,返回的状态不仅应包含已质押的代币数量,还应包含该地址是否存在任何排队中的质押事件。这要求在查询逻辑中完成更多工作,以追踪这些即将发生的、处于队列中的质押事件。 作为初始实现,可以对所有排队中的质押事件执行线性搜索。不过,对于需要较长 epoch 的链,最终应为支持查询的节点增加额外能力,以便能够在常数时间内产出结果。(这可以通过维护一个辅助哈希映射来实现,按地址索引即将发生的质押事件。) 步骤 3:调整 gas 当前,gas 表示交易被立即执行时的成本。(将 p2p 开销、状态访问开销和计算开销合并在一起。)不过现在,一笔交易可能会在未来的区块中触发计算,也就是在 epoch 边界处。 为处理这一点,我们应先加入用于估算未来计算量的参数(以 gas 计量),并将其作为该消息所需的固定收费项。 至于在 gas 定价中如何权衡未来计算与当前计算,不在本文讨论范围内;目前先设定两者权重相同。

影响

正面

  • 对权益证明模块进行了抽象,从而能够保留现有功能
  • 支持新的功能,例如基于验证者集合的门限密码学

负面

  • 提高了集成更复杂 gas 定价机制的复杂度,因为它们现在还必须考虑未来执行成本。
  • 当 epoch > 1 时,验证者将无法立即离开网络,而必须等待到某个 epoch 边界。

Changelog

  • 10-Feb-2021: Initial Draft

Authors

  • Dev Ojha (@valardragon)
  • Sunny Aggarwal (@sunnya97)

Status

Proposed

Abstract

This ADR updates the proof of stake module to buffer the staking weight updates for a number of blocks before updating the consensus’ staking weights. The length of the buffer is dubbed an epoch. The prior functionality of the staking module is then a special case of the abstracted module, with the epoch being set to 1 block.

Context

The current proof of stake module takes the design decision to apply staking weight changes to the consensus engine immediately. This means that delegations and unbonds get applied immediately to the validator set. This decision was primarily done as it was implementationally simplest, and because we at the time believed that this would lead to better UX for clients. An alternative design choice is to allow buffering staking updates (delegations, unbonds, validators joining) for a number of blocks. This ‘epoch’d proof of stake consensus provides the guarantee that the consensus weights for validators will not change mid-epoch, except in the event of a slash condition. Additionally, the UX hurdle may not be as significant as was previously thought. This is because it is possible to provide users immediate acknowledgement that their bond was recorded and will be executed. Furthermore, it has become clearer over time that immediate execution of staking events comes with limitations, such as:
  • Threshold based cryptography. One of the main limitations is that because the validator set can change so regularly, it makes the running of multiparty computation by a fixed validator set difficult. Many threshold-based cryptographic features for blockchains such as randomness beacons and threshold decryption require a computationally-expensive DKG process (will take much longer than 1 block to create). To productively use these, we need to guarantee that the result of the DKG will be used for a reasonably long time. It wouldn’t be feasible to rerun the DKG every block. By epoching staking, it guarantees we’ll only need to run a new DKG once every epoch.
  • Light client efficiency. This would lessen the overhead for IBC when there is high churn in the validator set. In the Tendermint light client bisection algorithm, the number of headers you need to verify is related to bounding the difference in validator sets between a trusted header and the latest header. If the difference is too great, you verify more header in between the two. By limiting the frequency of validator set changes, we can reduce the worst case size of IBC lite client proofs, which occurs when a validator set has high churn.
  • Fairness of deterministic leader election. Currently we have no ways of reasoning of fairness of deterministic leader election in the presence of staking changes without epochs (tendermint/spec#217). Breaking fairness of leader election is profitable for validators, as they earn additional rewards from being the proposer. Adding epochs at least makes it easier for our deterministic leader election to match something we can prove secure. (Albeit, we still haven’t proven if our current algorithm is fair with > 2 validators in the presence of stake changes)
  • Staking derivative design. Currently, reward distribution is done lazily using the F1 fee distribution. While saving computational complexity, lazy accounting requires a more stateful staking implementation. Right now, each delegation entry has to track the time of last withdrawal. Handling this can be a challenge for some staking derivatives designs that seek to provide fungibility for all tokens staked to a single validator. Force-withdrawing rewards to users can help solve this, however it is infeasible to force-withdraw rewards to users on a per block basis. With epochs, a chain could more easily alter the design to have rewards be forcefully withdrawn (iterating over delegator accounts only once per-epoch), and can thus remove delegation timing from state. This may be useful for certain staking derivative designs.

Design considerations

Slashing

There is a design consideration for whether to apply a slash immediately or at the end of an epoch. A slash event should apply to only members who are actually staked during the time of the infraction, namely during the epoch the slash event occurred. Applying it immediately can be viewed as offering greater consensus layer security, at potential costs to the aforementioned usecases. The benefits of immediate slashing for consensus layer security can be all be obtained by executing the validator jailing immediately (thus removing it from the validator set), and delaying the actual slash change to the validator’s weight until the epoch boundary. For the use cases mentioned above, workarounds can be integrated to avoid problems, as follows:
  • For threshold based cryptography, this setting will have the threshold cryptography use the original epoch weights, while consensus has an update that lets it more rapidly benefit from additional security. If the threshold based cryptography blocks liveness of the chain, then we have effectively raised the liveness threshold of the remaining validators for the rest of the epoch. (Alternatively, jailed nodes could still contribute shares) This plan will fail in the extreme case that more than 1/3rd of the validators have been jailed within a single epoch. For such an extreme scenario, the chain already have its own custom incident response plan, and defining how to handle the threshold cryptography should be a part of that.
  • For light client efficiency, there can be a bit included in the header indicating an intra-epoch slash (ala Link).
  • For fairness of deterministic leader election, applying a slash or jailing within an epoch would break the guarantee we were seeking to provide. This then re-introduces a new (but significantly simpler) problem for trying to provide fairness guarantees. Namely, that validators can adversarially elect to remove themself from the set of proposers. From a security perspective, this could potentially be handled by two different mechanisms (or prove to still be too difficult to achieve). One is making a security statement acknowledging the ability for an adversary to force an ahead-of-time fixed threshold of users to drop out of the proposer set within an epoch. The second method would be to parameterize such that the cost of a slash within the epoch far outweights benefits due to being a proposer. However, this latter criterion is quite dubious, since being a proposer can have many advantageous side-effects in chains with complex state machines. (Namely, DeFi games such as Fomo3D)
  • For staking derivative design, there is no issue introduced. This does not increase the state size of staking records, since whether a slash has occurred is fully queryable given the validator address.

Token lockup

When someone makes a transaction to delegate, even though they are not immediately staked, their tokens should be moved into a pool managed by the staking module which will then be used at the end of an epoch. This prevents concerns where they stake, and then spend those tokens not realizing they were already allocated for staking, and thus having their staking tx fail.

Pipelining the epochs

For threshold based cryptography in particular, we need a pipeline for epoch changes. This is because when we are in epoch N, we want the epoch N+1 weights to be fixed so that the validator set can do the DKG accordingly. So if we are currently in epoch N, the stake weights for epoch N+1 should already be fixed, and new stake changes should be getting applied to epoch N + 2. This can be handled by making a parameter for the epoch pipeline length. This parameter should not be alterable except during hard forks, to mitigate implementation complexity of switching the pipeline length. With pipeline length 1, if I redelegate during epoch N, then my redelegation is applied prior to the beginning of epoch N+1. With pipeline length 2, if I redelegate during epoch N, then my redelegation is applied prior to the beginning of epoch N+2.

Rewards

Even though all staking updates are applied at epoch boundaries, rewards can still be distributed immediately when they are claimed. This is because they do not affect the current stake weights, as we do not implement auto-bonding of rewards. If such a feature were to be implemented, it would have to be setup so that rewards are auto-bonded at the epoch boundary.

Parameterizing the epoch length

When choosing the epoch length, there is a trade-off queued state/computation buildup, and countering the previously discussed limitations of immediate execution if they apply to a given chain. Until an ABCI mechanism for variable block times is introduced, it is ill-advised to be using high epoch lengths due to the computation buildup. This is because when a block’s execution time is greater than the expected block time from Tendermint, rounds may increment.

Decision

Step-1: Implement buffering of all staking and slashing messages. First we create a pool for storing tokens that are being bonded, but should be applied at the epoch boundary called the EpochDelegationPool. Then, we have two separate queues, one for staking, one for slashing. We describe what happens on each message being delivered below:

Staking messages

  • MsgCreateValidator: Move user’s self-bond to EpochDelegationPool immediately. Queue a message for the epoch boundary to handle the self-bond, taking the funds from the EpochDelegationPool. If Epoch execution fail, return back funds from EpochDelegationPool to user’s account.
  • MsgEditValidator: Validate message and if valid queue the message for execution at the end of the Epoch.
  • MsgDelegate: Move user’s funds to EpochDelegationPool immediately. Queue a message for the epoch boundary to handle the delegation, taking the funds from the EpochDelegationPool. If Epoch execution fail, return back funds from EpochDelegationPool to user’s account.
  • MsgBeginRedelegate: Validate message and if valid queue the message for execution at the end of the Epoch.
  • MsgUndelegate: Validate message and if valid queue the message for execution at the end of the Epoch.

Slashing messages

  • MsgUnjail: Validate message and if valid queue the message for execution at the end of the Epoch.
  • Slash Event: Whenever a slash event is created, it gets queued in the slashing module to apply at the end of the epoch. The queues should be setup such that this slash applies immediately.

Evidence Messages

  • MsgSubmitEvidence: This gets executed immediately, and the validator gets jailed immediately. However in slashing, the actual slash event gets queued.
Then we add methods to the end blockers, to ensure that at the epoch boundary the queues are cleared and delegation updates are applied. Step-2: Implement querying of queued staking txs. When querying the staking activity of a given address, the status should return not only the amount of tokens staked, but also if there are any queued stake events for that address. This will require more work to be done in the querying logic, to trace the queued upcoming staking events. As an initial implementation, this can be implemented as a linear search over all queued staking events. However, for chains that need long epochs, they should eventually build additional support for nodes that support querying to be able to produce results in constant time. (This is do-able by maintaining an auxilliary hashmap for indexing upcoming staking events by address) Step-3: Adjust gas Currently gas represents the cost of executing a transaction when its done immediately. (Merging together costs of p2p overhead, state access overhead, and computational overhead) However, now a transaction can cause computation in a future block, namely at the epoch boundary. To handle this, we should initially include parameters for estimating the amount of future computation (denominated in gas), and add that as a flat charge needed for the message. We leave it as out of scope for how to weight future computation versus current computation in gas pricing, and have it set such that the are weighted equally for now.

Consequences

Positive

  • Abstracts the proof of stake module that allows retaining the existing functionality
  • Enables new features such as validator-set based threshold cryptography

Negative

  • Increases complexity of integrating more complex gas pricing mechanisms, as they now have to consider future execution costs as well.
  • When epoch > 1, validators can no longer leave the network immediately, and must wait until an epoch boundary.