美文网首页
Substrate的Staking模块分析

Substrate的Staking模块分析

作者: 建怀 | 来源:发表于2019-11-27 20:24 被阅读0次

    Substrate的Staking模块分析

    Staking模块被网络管理维护人员用来管理资产用来抵押。

    抵押模块是一组网络维护人员(可以称为authorities,也可以叫validators)根据那些自愿把资产存入抵押池中来选择的方法模块。这些在抵押池中的资金,正常情况下会获得奖励,如果发现维护人员没有正确履行职责,则会被没收。

    抵押模块术语

    • 抵押Staking:将资产锁定一段时间,使其面临大幅惩罚损失掉的风险,从而使其成为一个有回报的网络维护者的过程。
    • 验证Validating:运行一个节点来主动维护网络的过程,通过出块或保证链的最终一致性。
    • 提名Nominating:将抵押的资金置于一个或者多个验证者背后,以分享他们所接受的奖励和惩罚的过程。
    • 隐藏账号Stash account:持有一个所有者用于抵押的资金的账号。
    • 控制账号Controller account:控制所有者资金的账号。
    • 周期Era:验证者集合(和每个验证者的活跃提名集合)是在一个周期后需要重新计算的,并在那里支付奖励。
    • 惩罚Slash:通过减少抵押者的资产来惩罚抵押者。

    抵押模块的目标

    抵押系统在Substrate的NPoS共识机制下,被设计用来使得下面几个目标成为可能:

    • 抵押资产被一个冷钱包所控制。
    • 在不影响实体作用角色的情况下,提取或存入部分资产。
    • 以最小的开销可以在角色(提名者、验证者和空闲)之间切换。

    Staking 抵押

    几乎任何与Staking模块的交互都需要bonding进程,也就是账号要成为一个staker,账号要绑定,一个持有资金用来抵押的账号stash account,持有这部分资金被冻结,作为被抵押的资金,与此成对存在的是controller account,这个控制账号能发送操作控制指令。

    Validating 验证

    一个验证者它的角色是验证区块和保证区块最终一致性,维护网络的诚实。验证者应该避免任何恶意的错误行为和离线。声明有兴趣成为验证者的绑定账号不会立即被选择为验证者,相反,他们被宣布为候选人,他们可能在下届选举中被选为验证者。选举结果由提名者及其投票决定。

    Nominator 提名者

    一个提名者在维护网络时不承担任何直接的角色,而是对要选举的一组验证人进行投票。账号一旦声明了提名的利益,将在下一轮选举中生效。提名人stash账号中的资产表明其投票的重要性,验证者获得的奖励和惩罚都由验证者和提名者共享。这条规则鼓励提名者尽可能不要投票给行为不端/离线的验证者,因为如果提名者投错了票,他们也会失去资产。

    奖励和惩罚

    奖励和惩罚是抵押模块的核心,试图包含有效的行为,同时惩罚任何不当行为或缺乏可用性的行为。一旦有不当行为被报告,惩罚可以发生在任何时间点。一旦确定了惩罚,一个惩罚的值将从验证者的余额中扣除,同时也将从所有投票给该验证者的提名者的余额中扣除。与惩罚类似,奖励也在验证者和提名者之间共享。然而,奖励资金并不总是转移到stash账号。

    Chilling 冷却

    最后,上面的任何角色都可以选择暂时退一步,冷静一下。这意味着,如果他们是提名者,他们将不再被视为选民,如果他们是验证者,他们将不再是下次选举的候选人。

    实现细节

    Slot Stake

    这个周期SlotStake会在整个section都会被用到。在每个era结尾都会计算一个value,包含所有validators的最小抵押value,当然这个抵押value包含自己抵押的和接收到别人的抵押。

    Reward Calculation

    在每个era结尾都会给Validators和Nominators奖励,总的奖励根据era duration和抵押比例(总的抵押币/总的供应币)来计算。

    [authorship::EventHandler]在区块生产时增加奖励点。

    validator能宣称一个总额,被命名为[validator_payment],那个在每个奖励偿付过程中不会跟nominators分享。这个validator_payment的币会从总奖励总减出来,总的奖励是分给validator和nominator的,减去这个validator_payment之后,就会按照比例分割后奖励给validator和nominator。

    Additional Fund Management Operations

    任何放到stash账号下的资产都可以有如下操作的目标:

    • controller账号能够将部分比例(不是所有)资产自由化,使用[unbond]方法调用。当然这个资产不是立马就可以用,一个解冻周期[BondingDuration]需要经过后,才可以移动这部分资产。当这个解冻周期过去后,可以调用[withdraw_unbonded]方法来实际提取这笔资产。

    Election Algorithm

    现在的选举算法是基于Phragmen来实现的。

    选举算法除了选出具有最多利害关系值和票数的验证者外,还试图以平等的方式将提名者的选票分配给候选人。为了进一步确保这一点,可以应用一个可选的后处理,它迭代地规范化提名者所确定的值,直到某个提名者的投票总数差异小于阈值。

    GenesisConfig

    抵押模块是要依靠[GenesisConfig]。

    相关的模块:

    struct

    /// 一个era中所有奖励的点,用来将总点奖励支出在所有validators中切分
    #[derive(Encode, Decode, Default)]
    pub struct EraPoints {
        /// 奖励点点总数,等于给每个validator的奖励点总和 
        total: Points,
        /// 一个指定validator的奖励点,vec中的索引对应到现在validator集合中的索引
        individual: Vec<Points>,
    }
    
    /// 在一个惩罚事件上的优先级
    #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
    pub struct ValidatorPrefs<Balance: HasCompact> {
        /// 预先奖励验证者;只有其余的被他们自己和提名者瓜分。
        #[codec(compact)]
        pub validator_payment: Balance,
    }
    
    /// 只是用一个 Balance/BlockNumber 元组去编码时,一大块资金将解锁。
    #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
    pub struct UnlockChunk<Balance: HasCompact> {
        /// 将要被解锁的资金总额
        #[codec(compact)]
        value: Balance,
        /// era轮次,在这个轮次上,奖励点会解锁
        #[codec(compact)]
        era: EraIndex,
    }
    
    /// 一个绑定的stash账号账本
    #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
    pub struct StakingLedger<AccountId, Balance: HasCompact> {
        /// stash账号,其实际被锁定用来抵押的余额
        pub stash: AccountId,
        /// stash账号目前被统计的总币额,包括`active`加上`unlocking`的余额
        #[codec(compact)]
        pub total: Balance,
        /// stash账号中,将用来在任何将要来临轮次中抵押的总余额
        #[codec(compact)]
        pub active: Balance,
        /// stash中解锁的余额,将是变得自由,最终也会被转移出stash账号,当然其首先不要被惩罚掉
        pub unlocking: Vec<UnlockChunk<Balance>>,
    }
    
    /// 一个个人提名者拥有可以被惩罚掉的暴露(这个暴露包括账号和余额)å
    #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug)]
    pub struct IndividualExposure<AccountId, Balance: HasCompact> {
        /// 提名者的stash账号
        who: AccountId,
        /// 暴露的资金总额
        #[codec(compact)]
        value: Balance,
    }
    
    /// 系统中支持单个validator的抵押的快照。
    #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug)]
    pub struct Exposure<AccountId, Balance: HasCompact> {
        /// 支持这个validator的总币额
        #[codec(compact)]
        pub total: Balance,
        /// validator自己暴露的stash账号余额
        #[codec(compact)]
        pub own: Balance,
        /// 提名者暴露的stash账号和余额
        pub others: Vec<IndividualExposure<AccountId, Balance>>,
    }
    
    /// 一个惩罚事件被触发,惩罚validator一定数量的余额
    #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug)]
    pub struct SlashJournalEntry<AccountId, Balance: HasCompact> {
        who: AccountId,
        amount: Balance,
        own_slash: Balance, // the amount of `who`'s own exposure that was slashed
    }
    

    Storage

    /// 抵押参与的Validator数量
    pub ValidatorCount get(fn validator_count) config(): u32;
    /// 在紧急条件被强加的时候,抵押参与的Validator的最小数量,默认是4
    pub MinimumValidatorCount get(fn minimum_validator_count) config():
        u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT;
    
    /// 任何Validator,可能永远不会被惩罚或强行踢掉。它是一个Vec,因为它们易于初始化,并且性能影响很小(我们期望不超过4个Invulnerables),并且仅限于testnets。
    pub Invulnerables get(fn invulnerables) config(): Vec<T::AccountId>;
    
    /// controller账号控制的stash账号的Map
    pub Bonded get(fn bonded): map T::AccountId => Option<T::AccountId>;
    /// 所有controller账号对应到的抵押余额的Map
    pub Ledger get(fn ledger):map T::AccountId => Option<StakingLedger<T::AccountId, BalanceOf<T>>>;
    
    /// 当奖励发放的时候,key存放stash账号,value存放RewardDestination
    pub Payee get(fn payee): map T::AccountId => RewardDestination;
    
    /// validator的stash账号为key,value是validator自己的预先奖励
    pub Validators get(fn validators): linked_map T::AccountId => ValidatorPrefs<BalanceOf<T>>;
    
    /// 提名者自己stash账号为key,value为提名的所有validators
    pub Nominators get(fn nominators): linked_map T::AccountId => Vec<T::AccountId>;
    
    /// 某个Validator下面所有的抵押支持者,key为stash账号,value是Exposure
    pub Stakers get(fn stakers): map T::AccountId => Exposure<T::AccountId, BalanceOf<T>>;
    
    /// 现在当选的validators集合,stash账号ID被用来作为key
    pub CurrentElected get(fn current_elected): Vec<T::AccountId>;
    
    /// 现在era的索引 
    pub CurrentEra get(fn current_era) config(): EraIndex;
    
    /// 现在era的开始
    pub CurrentEraStart get(fn current_era_start): MomentOf<T>;
    
    /// 现在era开始的session索引
    pub CurrentEraStartSessionIndex get(fn current_era_start_session_index): SessionIndex;
    
    /// 现在era的奖励,使用现在当选集合的indices 
    CurrentEraPointsEarned get(fn current_era_reward): EraPoints;
    
    /// 对于没个validator slot中总的活跃余额
    pub SlotStake get(fn slot_stake) build(|config: &GenesisConfig<T>| {
        config.stakers.iter().map(|&(_, _, value, _)| value).min().unwrap_or_default()
    }): BalanceOf<T>;
    
    /// 是否强制开启新的era
    pub ForceEra get(fn force_era) config(): Forcing;
    
    /// 分给举报者的惩罚金额的比例,剩下的惩罚余额由`Slash`来处理
    pub SlashRewardFraction get(fn slash_reward_fraction) config(): Perbill;
    
    /// A mapping from still-bonded eras to the first session index of that era.
    BondedEras: Vec<(EraIndex, SessionIndex)>;
    
    /// 在一个指定era中,所有发生的惩罚
    EraSlashJournal get(fn era_slash_journal):
        map EraIndex => Vec<SlashJournalEntry<T::AccountId, BalanceOf<T>>>;
    

    初始化设定

    add_extra_genesis {
        config(stakers):
            Vec<(T::AccountId, T::AccountId, BalanceOf<T>, StakerStatus<T::AccountId>)>;
        build(|config: &GenesisConfig<T>| {
            for &(ref stash, ref controller, balance, ref status) in &config.stakers {
                assert!(
                    T::Currency::free_balance(&stash) >= balance,
                    "Stash does not have enough balance to bond."
                );
                let _ = <Module<T>>::bond(
                    T::Origin::from(Some(stash.clone()).into()),
                    T::Lookup::unlookup(controller.clone()),
                    balance,
                    RewardDestination::Staked,
                );
                let _ = match status {
                    StakerStatus::Validator => {
                        <Module<T>>::validate(
                            T::Origin::from(Some(controller.clone()).into()),
                            Default::default(),
                        )
                    },
                    StakerStatus::Nominator(votes) => {
                        <Module<T>>::nominate(
                            T::Origin::from(Some(controller.clone()).into()),
                            votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(),
                        )
                    }, _ => Ok(())
                };
            }
        });
    }
    

    Module

    • fn bond(origin,controller: <T::Lookup as StaticLookup>::Source,#[compact] value: BalanceOf<T>, payee: RewardDestination)
      • origin作为stash账号,controller账号控制这个stash账号,value用来锁定,当然要大于minimum_balance,payee是Stash,Staked和Controller三个选择
    • fn bond_extra(origin,#[compact] max_additional: BalanceOf<T>)
      • 增加stash账号下额外自由余额进入抵押
    • fn unbond(origin,#[compact] value: BalanceOf<T>)
      • 把抵押中部分解锁解绑出来,如果剩下的小于T::Currency::minimum_balance(),则直接全部解绑
    • fn withdraw_unboned(origin)
      • 提取解绑的余额出来,这个需要controller账号来签名发起请求
    • fn validate(origin,prefs: ValidatorPrefs<BalanceOf<T>>)
      • 一个提名者声明作为一个验证者
    • fn nominate(origin,targets: Vec<<T::Lookup as StaticLookup>::Source>)
      • 一个验证者声明做一个提名者
    • fn chill(origin)
      • 一个stash账号声明不做提名者和验证者
    • fn set_payee(origin,payee: RewardDestination)
      • 为一个controler设置payment
    • fn set_controller(origin, controller: <T::Lookup as StaticLookup>::Source)
      • 为一个stash账号设置一个controller账号
    • fn set_validator_count(origin,#[compact] new: u32)
      • 设置validators的理想数量
    • fn force_no_eras(origin)
      • Root调用,强制无限期的没有新的eras
    • fn force_new_eras(origin)
      • Root调用,在下一个session强制开启一个新的era
    • fn set_invulnerables(origin,validators: Vec<T::AccountId>)
      • Root调用,设置几个无敌账号,不会被惩罚到
    • fn force_unstake(origin,stash: T::AccountId)
      • Root调用,把某个抵押者直接剔除
    • fn force_new_era_always(origin)
      • Root调用,在sessions结束后无限制的开启一个新的era
    • pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T>
      • 对于一个validator的controller账号总余额能用来惩罚
    • fn update_ledger(controller: &T::AccountId,ledger: &StakingLedger<T::AccountId,BalanceOf<T>>)
      • 更新一个controller的ledger,这会更新stash的锁,会锁死所有资产,除了在接下来交易中的支付资产
    • fn slash_validator(stash: &T::AccountId,slash: BalanceOf<T>,exposure: &Exposure<T::AccountId,BalanceOf<T>>,journal: &mut Vec<SlashJournalEntry<T::AccountId,BalanceOf<T>>>) -> NegativeImbalanceOf<T>
      • 惩罚给定的validator,通过给定的exposure计算出具体的金额
    • fn make_payout(stash: &T::AccountId, amout: BalanceOf<T>) -> Option<PositiveImbalanceOf<T>>
      • 给一个抵押者实际的奖励支付
    • fn reward_validator(stash: &T::AccountId, reward: BalanceOf<T>) -> PositiveImbalanceOf<T>
      • 奖励一个validator指定的金额,添加奖励给到validator和其nominators的余额,当然在这之前,需要提前支付掉validator的提前支付,按照两者的exposure的比例来发放奖励
    • fn new_session(session_index: SessionIndex) -> Option<(Vec<T::AccountId>,Vec<(T::AccountId,Exposure<T::AccountId,BalanceOf<T>>)>)>
      • session刚结束,提供一个validators集合给下一个session,如果在一个era的结束,同时也有之前validator集合的exposure
    • fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>>
      • era已经改变了,实施新的抵押集合
    • fn select_validators() -> (BalanceOf<T>, Option<Vec<T::AccountId>>)
      • 从组装的stakers及其角色首选项中选择一个新的validator集合
    • fn kill_stash(stash: &T::AccountId)
      • 从一个抵押系统中移除一个stash账号所有相关数据
    • pub fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>)
      • 使用validators的stash账号ID添加为奖励点
    • pub fn reward_by_indices(validators_points: impl IntoIterator<Item = (u32, u32)>)
      • 使用validator的索引作为奖励点
    • fn ensure_new_era()
      • 确保在现在session的结束后有一个新的era

    结合Runtime

    impl balances::Trait for Runtime {
        ...
        type OnFreeBalanceZero = ((staking,Contracts),Session); // 1. OnFreeBalanceZero跟staking什么关系?
        ...
    }
    
    impl authorship::Trait for Runtime {
        ...
        type EventHandler = (staking,ImOnline); // 2. 出块节点如何跟staking结合?
    }
    
    impl session::Trait for Runtime {
        type OnSessionEnding = Staking; // 3. session ending跟Staking如何关联呢?
        ...
        type ValidatorIdOf = staking::StashOf<Self>; // 4. ValidatorIdOf集合跟staking如何关联呢?
        type SelectInitialValidators = Staking; // 5. 选择初始Validator集合跟Staking如何关联呢?
    }
    
    impl session::historical::Trait for Runtime {
        type FullIdentification = staking::Exposure<AccountId, Balance>; // 6. session模块中用到的所有身份标记跟staking是如何关联?
        type FullIdentificationOf = staking::ExposureOf<Runtime>;
    }
    
    impl staking::Trait for Runtime {
        type Currency = Balances;
        type Time = Timestamp;
        type CurrencyToVote = CurrencyToVoteHandler;
        type RewardRemainder = Treasury; // 7. 国库如何跟RewardRemainder结合?
        type Event = Event;
        type Slash = Treasury; // 惩罚资金进入国库
        type Reward = (); // 奖励来自铸币
        type SessionsPerEra = SessionPerEra;
        type SessionInterface = Self;
        type RewardCurve = RewardCurve; // 8. NPoS的RewardCurve如何跟staking关联?
    }
    
    impl offences::Trait for Runtime {
        ...
        type OnOffenceHandler = Staking; // 9. Staking中的offences处理是怎么样的?
    }
    

    1. OnFreeBalanceZero跟staking什么关系?

    在balances模块中,on_free_too_low(who: &T::AccountId)会调用OnFreeBalanceZero::on_free_balance_zero(who),背后的意思也非常简单,当一个账号自由余额太低,被系统收割的时候,其validator和nominator中抵押的币都会被清理掉。注意,validator和nominator抵押的币都是free balance,只是加上了资产锁。

    2. 出块节点如何跟staking结合?

    在staking模块中,增加奖励点给到区块生产者:

    如果是非叔块的生产,就拿出20个奖励点去给到区块生产者进行奖励。

    如果是之前未被引用过的叔块,对叔块的每个引用给2个奖励点给区块生产者进行奖励。

    如果是被引用过叔块,对叔块的每个引用给一个奖励点给区块生产者进行奖励。

    impl<T: Trait + authorship::Trait> authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T> {
        fn note_author(author: T::AccountId) {
            Self::reward_by_ids(vec![(author, 20)]);
        }
        fn note_uncle(author: T::AccountId, _age: T::BlockNumber) {
            Self::reward_by_ids(vec![
                (<authorship::Module<T>>::author(), 2),
                (author, 1)
            ])
        }
    }
    

    在authorship模块中,note_author()note_uncle()方法:

    pub trait EventHandler<Author, BlockNumber> {
        /// Note that the given account ID is the author of the current block.
        fn note_author(author: Author);
    
        /// Note that the given account ID authored the given uncle, and how many
        /// blocks older than the current block it is (age >= 0, so siblings are allowed)
        fn note_uncle(author: Author, age: BlockNumber);
    }
    

    Module中:

    fn on_initialize(now: T::BlockNumber){
        ...
        T::EventHandler::note_author(Self::author());
    }
    

    3. session ending跟Staking如何关联呢?

    在staking模块中:

    impl<T: Trait> session::OnSessionEnding<T::AccountId> for Module<T> {
        fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option<Vec<T::AccountId>> {
            Self::new_session(start_session - 1).map(|(new, _old)| new)
        }
    }
    
    impl<T: Trait> OnSessionEnding<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>> for Module<T> {
        fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex)
            -> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
        {
            Self::new_session(start_session - 1)
        }
    }
    

    在session的Module里:

    pub fn rotate_session(){
        ...
        // Get next validator set.
        let maybe_next_validators = T::OnSessionEnding::on_session_ending(session_index, applied_at);
    }
    

    根据staking中抵押来获取下一个session的validator集合,然后session模块中调用。

    4. Validator集合跟staking如何关联呢?

    impl<T: Trait> SessionInterface<<T as system::Trait>::AccountId> for T where
        ...
        T::ValidatorIdOf: Convert<<T as system::Trait>::AccountId, Option<<T as system::Trait>::AccountId>>
        
    /// 一个`Convert`实现能够找到stash账号从这个给定的controller账号中 
    pub struct StashOf<T>(rstd::marker::PhantomData<T>);
    
    impl<T: Trait> Convert<T::AccountId, Option<T::AccountId>> for StashOf<T> {
        fn convert(controller: T::AccountId) -> Option<T::AccountId> {
            <Module<T>>::ledger(&controller).map(|l| l.stash)
        }
    }
    
    /// 一个从stash账号ID到现在那个账号的提名者的exposure的转换
    pub struct ExposureOf<T>(rstd::marker::PhantomData<T>);
    
    impl<T: Trait> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>>>>
        for ExposureOf<T>
    {
        fn convert(validator: T::AccountId) -> Option<Exposure<T::AccountId, BalanceOf<T>>> {
            Some(<Module<T>>::stakers(&validator))
        }
    }
    

    在session模块Module中

    fn set_keys(origin,keys: T::Keys, proof: Vec<u8>) -> Result {
        ...
        let who = match T:: ValidatorIdOf::convert(who) {
            Some(val_id) => val_id,
                None => return Err("no associated validator ID for account."),
            };
        }
    }
    

    允许一个账号设置其session key去成为一个验证者。

    5. 选择初始Validator集合跟Staking如何关联呢?

    在staking模块中:

    impl<T: Trait> SelectInitialValidators<T::AccountId> for Module<T> {
        fn select_initial_validators() -> Option<Vec<T::AccountId>> {
            <Module<T>>::select_validators().1
        }
    }
    

    在session模块的decl_storage中:

    add_extra_genesis {
        config(keys): Vec<(T::ValidatorId, T::Keys)>;
        build(|config: &GenesisConfig<T>| {
        ...
        let initial_validators = T::SelectInitialValidators::select_initial_validators()
                .unwrap_or_else(|| config.keys.iter().map(|(ref v, _)| v.clone()).collect());
        ...
    

    在session模块中,初始化配置选择validator。

    6. session模块中用到的所有身份标记跟staking是如何关联?

    FullIdentification是出块认证者的身份标记,

    T: session::historical::Trait<
        FullIdentification = Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>,
        FullIdentificationOf = ExposureOf<T>,
    >,
    
    pub Stakers get(fn stakers): map T::AccountId => Exposure<T::AccountId, BalanceOf<T>>;
    

    7. 国库如何跟RewardRemainder结合?

    在staking模块中

    /// Tokens have been minted and are unused for validator-reward.
    type RewardRemainder: OnUnbalanced<NegativeImbalanceOf<Self>>;
    

    在staking模块的Module中:

    fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
        ...
        T::RewardRemainder::on_unbalanced(T::Currency::issue(rest));
    }
    

    on_unbalanced()方法是support模块中的:

    pub trait OnUnbalanced<Imbalance: TryDrop> {
        /// Handler for some imbalance. Infallible.
        fn on_unbalanced(amount: Imbalance) {
            amount.try_drop().unwrap_or_else(Self::on_nonzero_unbalanced)
        }
    
        /// Actually handle a non-zero imbalance. You probably want to implement this rather than
        /// `on_unbalanced`.
        fn on_nonzero_unbalanced(amount: Imbalance);
    }
    

    当账号下余额减少了,需要处理这种不平衡,validator奖励是余额增加的原因,余额减少的原因是惩罚之类的。

    OnUnbalanced<I> for SplitTwoWays<Balance, I, Part1, Target1, Part2, Target2>
    {
        fn on_nonzero_unbalanced(amount: I) {
            let total: u32 = Part1::VALUE + Part2::VALUE;
            let amount1 = amount.peek().saturating_mul(Part1::VALUE.into()) / total.into();
            let (imb1, imb2) = amount.split(amount1);
            Target1::on_unbalanced(imb1);
            Target2::on_unbalanced(imb2);
        }
    }
    

    在runtime中可以找到:

    pub type DealWithFees = SplitTwoWays<
        Balance,
        NegativeImbalance,
        _4, Treasury,   // 4 parts (80%) goes to the treasury.
        _1, Author,     // 1 part (20%) goes to the block author.
    >;
    

    手续费和惩罚80%进入国库,20%给到出块认证者。

    8. NPoS的RewardCurve如何跟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,
        );
    }
    

    在staking模块中:

    /// The NPoS reward curve to use.
    type RewardCurve: Get<&'static PiecewiseLinear<'static>>;
    

    new_era()方法中,用到了RewardCurve:

    fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
        ...
        let (total_payout, max_payout) = inflation::compute_total_payout(
            &T::RewardCurve::get(),
            total_rewarded_stake.clone(),
            T::Currency::total_issuance(),
            // Duration of era; more than u64::MAX is rewarded as u64::MAX.
            era_duration.saturated_into::<u64>(),
        );
    

    简单理解下这个compute_total_payout()方法,在inflation.rs中是根据era持续周期和抵押率(提名者和验证者抵押除以总发行)来计算总的奖励。

    其计算公式如下:

    payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year
    

    era_duration是毫秒单位。

    9. Staking中的offences处理是怎么样的?

    在staking模块中:

    fn on_offence(
        offenders: &[OffenceDetails<T::AccountId, session::historical::IdentificationTuple<T>>],
        slash_fraction: &[Perbill],
    ) {}
    

    在offences模块Module中:

    fn report_offence(reporters: Vec<T::AccountId>, offence: O) {
        ...
        T::OnOffenceHandler::on_offence(&concurrent_offenders, &slash_perbill);
    }

    相关文章

      网友评论

          本文标题:Substrate的Staking模块分析

          本文链接:https://www.haomeiwen.com/subject/wxymwctx.html