风险提示:请理性看待区块链,树立正确的货币观念和投资理念,不要盲目跟风投资,本站内容不构成投资建议,请谨慎对待。 免责声明:本站所发布文章仅代表个人观点,与CoinVoice官方立场无关

开发 | Substrate 中的 Staking Pallet

PolkaWorld
2020年12月02日

本文作者为 Patract Labs 开发者张泰林。

目录:

  • 生成账户(Accounts)

  • 质押资金(fn bond)

  • 设置验证人(fn validate)

  • 提名验证人(fn nominate)

  • 冻结验证人或提名人(fn chill)

  • Phragmén 选举算法

  • 奖励结算

    • 通货膨胀模型
  • 奖励分配(fn do_payout_stakers)

  • 奖励认领

Staking 是什么

Staking 是管理网络维护者的抵押资金的模块。网络维护者也称为 Authorities (出块人) 或者 Validators (验证人),它们是基于抵押资金被选中,它们在正常履行职责的情况下会获得奖励,如果行为不当则会受到惩罚 (没收一定的资金)。

验证人、提名人

Staking Module 有两个重要的角色:验证人和提名人

  • 验证人(Validator): 运行节点以主动维护网络,并履行生成块或保证链的最终性的职责。

  • 提名人(Nominator): 将抵押资金分配给一个或多个验证人的过程,以便他们分享任何报酬和惩罚。

如何成为验证人 / 提名人

生成账户(Accounts)

用户可以通过 Polkadot 钱包或 https://polkadot.js.org/apps/#/accounts 生成自己的账户,注意保管好私钥和助记词。

为了保障用户资金安全,Staking Module 设计了两层结构的独立密钥类型,采用两个不同的账户来管理资金, 我们称为:存储账户(Stash Account)和控制账户(Controller Account)(就好比房东和中介的关系)

开发 | Substrate 中的 Staking Pallet

  • Stash :存储账户主要用来存放用于质押的资金,存储账户可以指定一个控制账户,将申请提名人、验证人等功能委托给控制账户,存储账户的密钥可以长期保存在冷钱包中,以此保证用户资金的安全性。

  • Controller:控制帐户是存储账户的代理,有申请提名人和验证人、设置收款账户和佣金的权利。如果是验证人,它还可以设置 session keys 会话密钥。只需要保证控制账户有足够的资金来支付交易手续费。

质押资金(fn bond)

用户需要质押一定的资金(质押金额不得小于限定的最小金额)来获取成为验证人或提名人的资格,质押行为由存储账户发起,质押过程可以设置控制账户、质押金额、收款账户。

开发 | Substrate 中的 Staking Pallet

控制账户推荐是一个与存储账户不同的账户,以此保证存储账户的安全,当然也可以设置成存储账户。一个存储账户只能由一个控制账户代理,一个控制账户只能代理一个存储账户。质押完成后质押的资金将被锁定。

收款账户是用来接收 staking 奖励的,有四个可选设置:

  1. Staked:奖励支付给存储账户并用来质押

  2. Stash:奖励支付给存储账户,但奖励不用来质押

  3. Contriller:奖励支付给控制账户

  4. Account:奖励支付给一个指定账户

Staking Module 提供了以下几种质押相关的功能:

  • bond:质押,由存储账户调用,质押资金

  • bond_extra:额外质押,已经调用过 bond 的存储账户,再次质押存储账户的 free_balance 资金

  • unbond:解除质押,由控制账户调用(EraElectionStatusis Closed),资金解除质押后处于解冻中状态

  • withdraw_unbonded:取回解冻资金,由控制账户调用(EraElectionStatusis Closed),由于资金在质押后有锁定时长限定,在波卡上锁定 28 天,Kusama 上是锁定 7 天,unbonded 的资金也需要过了锁定期(从 unbond 开始计算)后才能被取出 , 如果验证人被发现行为不端,将受到惩罚

  • rebond:再次质押,由控制账户调用(EraElectionStatusis Closed), 将处于解冻中的资金再次质押

设置验证人(fn validate)

成为验证人过程比较繁琐,这里就简述下步骤,详见:How to run a Validator on Polkadot

  1. 运行 Polkadot 验证人节点 , 并同步到链的最新状态

  2. 质押 DOT

  3. 设置 Session keys (Submitting the setKeys Transaction)

  4. 设置验证人,包括设置验证人的佣金,佣金是按比例收取的,当分配 staking 奖励时,会优先支付验证人的佣金,剩余奖励才会分配给提名人。

开发 | Substrate 中的 Staking Pallet

注意:同一个 stash account 只能成为验证人或提名人,验证人可以通过自抵押的方式提名自己,但不可以通过提名的方式。已经是提名人的 stash account 不可以作为验证人。

提名验证人(fn nominate)

控制账户可以提交一份支持的信誉良好的候选验证人名单(提名最多只能有 16 个 , 即 MAX_NOMINATIONS )成为提名人。在下一个 Era,具有最多 DOT 支持的一定数量的验证人被选中,如果提名人支持的验证人被选中,就可以分享验证人出块奖励或惩罚。提名过程只能发生在非候选验证人选举阶段。

开发 | Substrate 中的 Staking Pallet

一旦提名阶段结束,NPoS 选举机制以提名人及其投票为输入,输出一组数量有限的验证人,使任何验证人的支持度最大化,并尽可能均匀分布。这种选举机制的目标是最大限度地提高网络的安全性,实现提名人的公平代表。

冻结验证人或提名人(fn chill)

冻结是从活跃验证人节点池中移除验证人的行为,同时在下一个 Era 中取消它们在可选择候选人 list 中的资格。

冻结可以是自愿性的,并且是验证人可以决定的,例如,如果验证人的环境或服务器提供商有计划的中断,并且验证人希望退出以保护自己不被 slash。当发生自愿行为时,那么冻结将使验证人在当前会话中保持活跃状态,但会在下一个会话中将它们移动到非活跃集。验证人不会失去他们的提名人。

当被用作惩罚的一部分时,被冻结意味着未被提名。它还会在当前剩余的 era 中丧失验证人的能力,并在下一次选举中移除违规的验证人。

Polkadot 允许禁用一些验证人,但是如果禁用的验证人的数量太大,Polkadot 将触发一个新的验证人选举以获取完整的验证人节点池。禁用的验证人需要重新提交他们的验证意图和重新获得提名人的支持。

NPoS 机制

NPoS (Nominated Proof of Stake,提名权益证明)是 Polkadot 基于 PoS 算法设计的共识算法,验证人( Validator)运行节点参与生产和确认区块,提名人(Nominator)可以抵押自己的 dot 获得提名权,并提名自己信任的验证人,获得奖励。

Phragmén 选举算法

验证人选举算法是 NPoS 机制的核心,选举过程要具有公平代表性和安全性,Polkadot 为此设计了 Phragmén 算法 , 确保每次选举都具有这种性质。

  • 公平代表性:任何持有总股份至少 1/n 的提名人都保证至少有一个他们信任的验证人当选

  • 安全性:我们希望尽可能让对候选验证人很难获得一个验证人,他们只有得到足够高的支持才能做到这一点。因此,我们将选举结果的安全级别等同于被选验证人的最小支持数量。

下图一不具备公平代表性,图二具备公平代表性,并且图二右的安全级别更高。开发 | Substrate 中的 Staking Pallet

开发 | Substrate 中的 Staking Pallet

Polkadot 每进入 new_session,都有可能触发进入 new_era,进入新的 Era 后,会通过 Phragmen 算法重新选出一组验证人。Phragmen 算法的计算可以是 off-chain 的,也可以是 on-chain,会优先从 QueuedElected 中读取选举结果,QueuedElected 的结果是由 offchain-workers 计算提交的, 如果没有,就会执行 on-chain 计算选举。

小知识:Kusama 的运行速度是波卡的 4 倍,除了区块时间都是 6 秒。

周期 Polkadot Kusama
Slot 6 秒 6 秒
Epoch 4 小时 1 小时
Session 4 小时 1 小时
Era 24 小时 6 小时

验证人数量的上限尚未确定,但仅受限于由频繁和大量的点对点消息传递而造成的网络带宽紧张。Polkadot 在网络成熟的时候将拥有约为 1000 个验证人。而波卡的金丝雀网络 Kusama 目前有 700 个验证人插槽,Polkadot 主网目前有 242 个验证人插槽(截止 2020/11/27)。

Staking 奖励

奖励结算

每个区块生成后,authorship->on_initialize 会记录(note_author->reward_by_ids)区块生产者的 ErasRewardPoints, 并在每个 end_era 进行结算。Kusama 上每天计算四次奖励,在波卡上每天计算一次奖励。

Reward Points 增加规则:

  • 主链区块生产者增加 20 点 reward

  • 叔区块生产者增加 2 点 reward

  • 引用叔区块的生产者增加 1 点 reard

奖励结算规则(fn compute_total_payout):

  1. 根据年膨胀率计算当前 era 获得的实际奖励
    // era 奖励 = 年膨胀率 * 通证发行总量 / 每年 era 个数  
    staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year  
  1. 根据最大年膨胀率计算当前 era 获得的最大奖励
    max_payout = max_yearly_inflation * total_tokens / era_per_year  
  1. 如果最大奖励减去实际奖励还有剩余奖励,将剩余奖励收归国库,用于支持生态发展支出
    type RewardRemainder = Treasury;  

    let rest = max_payout.saturating_sub(staker_payout);  

    T::RewardRemainder::on_unbalanced(T::Currency::issue(rest));  

通货膨胀模型

Staking 的奖励主要来源于 DOT 代币增发,这也是 DOT 主要的通胀来源。Polkadot 希望有 50% 的代币被抵押到 NPoS 共识系统,30% 的代币用于平行链插槽拍卖,20% 代币在交易市场上流通。而在通胀率上,Polkadot 希望是每年 10%,在 50% 的抵押率中,抵押代币的平均年化收益为 20%。

开发 | Substrate 中的 Staking Pallet上图中,横坐标为抵押率,蓝色线纵坐标为年通胀率,绿色线纵坐标为年化收益率。

从图中可以看出:

  1. 当抵押率小于 50%,年化收益率大于 20%,这会鼓励用户抵押 DOT;

  2. 当抵押率等于 50%,年化收益率等于 20%,符合设计预期;

  3. 当抵押率大于 50%,年化收益率小于 20%,这会鼓励用户赎回 DOT。

Staking 奖励曲线

    pallet_staking_reward_curve::build! {  
     const REWARD_CURVE: PiecewiseLinear<'static> = curve!(  
      min_inflation: 0_025_000,  
      max_inflation: 0_100_000,  
      ideal_stake: 0_500_000,  
      falloff: 0_050_000,  
      max_piece_count: 40,  
      test_precision: 0_005_000,  
     );  
    }  

奖励分配(fn do_payout_stakers)

  • 两个验证人在相同的工作中获得相同数量的 DOT,即它们的报酬与每个验证人的 stake 质押数量不成比例

  • 奖励的一部分(commission 按奖励百分比设置)优先用于支付验证人的佣金,其余部分按比例(即与 stake 成比例)支付给提名人和验证人

  • 验证人将获得两次奖励:一次作为验证的佣金,一次作为用自抵押提名自己的佣金

    fn do_payout_stakers(  
        validator_stash: T::AccountId,  
        era: EraIndex,  
    ) -> DispatchResult {  
        // Validate input data  
        ......  

        /* Input data seems good, no errors allowed after this point */  

        // 1. 获取当前 era 所有的 ErasRewardPoints  
        let era_reward_points = >::get(&era;);  
        let total_reward_points = era_reward_points.total;  

        // 2. 获取该验证人获得的所有 Reward Points  
        let validator_reward_points = era_reward_points.inpidual.get(&ledger.stash;)  
            .map(|points| *points)  
            .unwrap_or_else(|| Zero::zero());  

        // Nothing to do if they have no reward points.  
        if validator_reward_points.is_zero() { return Ok(())}  

        // 3. 计算该验证人奖励点所占比例根据比例得到该验证人所得奖励  
        // This is the fraction of the total reward that the validator and the  
        // nominators will get.  
        let validator_total_reward_part = Perbill::from_rational_approximation(  
            validator_reward_points,  
            total_reward_points,  
        );  
        // This is how much validator + nominators are entitled to.  
        let validator_total_payout = validator_total_reward_part * era_payout;  

        // 4. 优先支付验证人的佣金  
        let validator_prefs = Self::eras_validator_prefs(&era;, &validator;_stash);  
        // Validator first gets a cut off the top.  
        let validator_commission = validator_prefs.commission;  
        let validator_commission_payout = validator_commission * validator_total_payout;  
        let validator_leftover_payout = validator_total_payout - validator_commission_payout;  

        // 5. 支付验证人质押获得的奖励  
        // Now let's calculate how this is split to the validator.  
        let validator_exposure_part = Perbill::from_rational_approximation(  
            exposure.own,  
            exposure.total,  
        );  
        let validator_staking_payout = validator_exposure_part * validator_leftover_payout;  
        // We can now make total validator payout:  
        if let Some(imbalance) = Self::make_payout(  
            &ledger.stash;,  
            validator_staking_payout + validator_commission_payout  
        ) {  
            Self::deposit_event(RawEvent::Reward(ledger.stash, imbalance.peek()));  
        }  

        // 6. 支付提名人质押获得的奖励  
        // Lets now calculate how this is split to the nominators.  
        // Reward only the clipped exposures. Note this is not necessarily sorted.  
        for nominator in exposure.others.iter() {  
            let nominator_exposure_part = Perbill::from_rational_approximation(  
                nominator.value,  
                exposure.total,  
            );  

            let nominator_reward: BalanceOf = nominator_exposure_part * validator_leftover_payout;  
            // We can now make nominator payout:  
            if let Some(imbalance) = Self::make_payout(&nominator.who;, nominator_reward) {  
                Self::deposit_event(RawEvent::Reward(nominator.who.clone(), imbalance.peek()));  
            }  
        }  

        Ok(())  
    }  

奖励认领

每个人都可以选择触发无人认领的 指定 era 中某个验证人的 staking 奖励(payout_stakers),认领后奖励将支付给每个在那个 era 提名的提名人及验证人。由于 history_depth 默认设置为 84,即 staking 奖励信息只在最新的 84 个 Era 内有效,在波卡上约为 84 天,Kusama 上约 21 天。为了获得你的 staking 奖励,必须有人为你提名的每个验证人进行领取。

问题:为什么需要认领,以及为什么不直接认领一个 era 的奖励?

答案参考:https://wiki.polkadot.network/docs/en/learn-simple-payouts#docsNav

总结得到以下几点:

  • 通过提交交易领取奖励,将 staker 的账户更新分散到多个区块中,这比所有奖励集中在一个区块中,更能保证网络的稳定性。

  • 如果有攻击者使用成百上千个提名一个验证人,账户存储更新的费用,不应该由 Polkdot 来承担

  • 奖励只支付给质押最高的 256 位提名人,降低 staking 集合的复杂性(文档中说,但代码没看到)

惩罚(Slash)

如果验证人在网络中行为不当(例如脱机、攻击网络或运行修改过的软件),则会发生 slash 惩罚。他们和他们的提名人会因为 slash 惩罚而失去一部分 DOT。总质押数较大的验证池将受到更严厉的 slash 惩罚。在 start_era 时会对 slash 进行清算处理。

在 pallet-offences 模块中定义了三种违规行为:

  1. UnresponsivenessOffence (无响应)

  2. GrandpaEquivocationOffence (重复签名 ,voting)

  3. BabeEquivocationOffence (重复出块)

如果验证人因任何一项违规行为被举报,他们将被从验证人节点池中移除(也就是冻结),并且在他们移除的时候不会得到奖励。他们将立即被视为不活跃的验证人,并将失去提名人。他们需要重新发布验证意图和收集提名人的支持。

跨 Era 的 Slash 惩罚在 NPoS 中计算有三大难点:

  1. 一个提名人可以提名多个验证人,并通过其中任何一个被 slash 。

  2. 在被 slash 之前,这些 stake 质押会被一个 era 一个 era 的重复使用。连续为 E 个 era 提名 N 个代币并不意味着你有 N*E 个代币用于 slash,你还是只有 N 个代币。

  3. 可惩罚的违规行为还可以在事情发生后才被发现,且不是按照顺序的。

为了平衡以上的几点,我们只对参与者在某个时间段内可以收到的最大惩罚进行惩罚,而不是所有惩罚的加总。这样可以防止过度 slash。同样地,计算最大 slash 的时间跨度是有限的,验证人在 slash 事件后被冻结并撤回提名,如前一节所述。这可以防止怒退(rage-quit)攻击,在这种攻击中,一旦被发现行为不端,参与者故意行为不端的程度会更高,因为反正他们的 slash 数额已经达到最大值。

References


想要在波卡生态中开发新项目吗?由 PolkaWorld 社区、数秦科技、巴比特、Nano MG 创新空间、imToken、SimpleChain、IOSG、Acala Network、Digital Renaissance Foundation、Patract Labs、Polkadot 生态研究院、白话区块链、Dorahacks 联合发起等发起的「Substrate 创业营」 正在招募中,助力 Web3.0 生态创新产品的诞生和成长!点此了解详情和报名。

开发 | Substrate 中的 Staking Pallet

  • 欢迎学习 Substrate:

https://substrate.dev/

  • 关注 Substrate 进展 :

https://github.com/paritytech/substrate

  • 关注 Polkadot 进展 :

https://github.com/paritytech/polkadot

开发 | Substrate 中的 Staking Pallet

更多内容:

怎样使用 Substrate 做一个能安全支持百万地址的热钱包?

Substrate 入门 - Runtime 的 Wasm 与 native (九)

深入了解 Substrate 2.0

扫码关注公众号,回复 “1” 加入波卡群

开发 | Substrate 中的 Staking Pallet

关注 PolkaWorld

发现 Web 3.0 时代新机遇

点个 “在看” 再走吧!


声明:本内容为作者独立观点,不代表 CoinVoice 立场,且不构成投资建议,请谨慎对待,如需报道或加入交流群,请联系微信:VOICE-V。

评论0条

PolkaWorld

简介:波卡(Polkadot)第一中文社区,带你寻找 Web 3.0 时代新机遇!

专栏

更多>>