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三个选择
- origin作为stash账号,controller账号控制这个stash账号,value用来锁定,当然要大于
- 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);
}
网友评论