本文作者為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)(就好比房東和中介的關係)

Stash :存儲賬戶主要用來存放用於質押的資金,存儲賬戶可以指定一個控制賬戶,將申請提名人、驗證人等功能委託給控制賬戶,存儲賬戶的密鑰可以長期保存在冷錢包中,以此保證用戶資金的安全性。 Controller:控制帳戶是存儲賬戶的代理,有申請提名人和驗證人、設置收款賬戶和佣金的權利。如果是驗證人,它還可以設置session keys 會話密鑰。只需要保證控制賬戶有足夠的資金來支付交易手續費。

質押資金(fn bond)

用戶需要質押一定的資金(質押金額不得小於限定的最小金額)來獲取成為驗證人或提名人的資格,質押行為由存儲賬戶發起,質押過程可以設置控制賬戶、質押金額、收款賬戶。

控制賬戶推薦是一個與存儲賬戶不同的賬戶,以此保證存儲賬戶的安全,當然也可以設置成存儲賬戶。一個存儲賬戶只能由一個控制賬戶代理,一個控制賬戶只能代理一個存儲賬戶。質押完成後質押的資金將被鎖定。

收款賬戶是用來接收staking獎勵的,有四個可選設置:

Staked:獎勵支付給存儲賬戶並用來質押Stash:獎勵支付給存儲賬戶,但獎勵不用來質押Contriller:獎勵支付給控制賬戶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

運行Polkadot驗證人節點,並同步到鏈的最新狀態質押DOT設置Session keys(Submitting the setKeys Transaction)設置驗證人,包括設置驗證人的佣金,佣金是按比例收取的,當分配staking獎勵時,會優先支付驗證人的佣金,剩餘獎勵才會分配給提名人。

注意:同一個stash account 只能成為驗證人或提名人,驗證人可以通過自抵押的方式提名自己,但不可以通過提名的方式。已經是提名人的stash account 不可以作為驗證人。

提名驗證人(fn nominate)

控制賬戶可以提交一份支持的信譽良好的候選驗證人名單(提名最多只能有16個,即MAX_NOMINATIONS )成為提名人。在下一個Era,具有最多DOT 支持的一定數量的驗證人被選中,如果提名人支持的驗證人被選中,就可以分享驗證人出塊獎勵或懲罰。提名過程只能發生在非候選驗證人選舉階段。

一旦提名階段結束,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 的提名人都保證至少有一個他們信任的驗證人當選安全性:我們希望盡可能讓對候選驗證人很難獲得一個驗證人,他們只有得到足夠高的支持才能做到這一點。因此,我們將選舉結果的安全級別等同於被選驗證人的最小支持數量。

下圖一不具備公平代表性,圖二具備公平代表性,並且圖二右的安全級別更高。

Polkadot 每進入new_session,都有可能觸發進入new_era,進入新的Era後,會通過Phragmen算法重新選出一組驗證人。 Phragmen算法的計算可以是off-chain的,也可以是on-chain,會優先從QueuedElected中讀取選舉結果,QueuedElected的結果是由offchain-workers計算提交的, 如果沒有,就會執行on-chain計算選舉。

小知識:Kusama 的運行速度是波卡的4 倍,除了區塊時間都是6 秒。

週期PolkadotKusamaSlot6秒6秒Epoch4小時1小時Session4小時1小時Era24小時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):

根據年膨脹率計算當前era獲得的實際獎勵

// era獎勵 = 年膨脹率 * 通證發行總量 / 每年era個數staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year

根據最大年膨脹率計算當前era獲得的最大獎勵

max_payout = max_yearly_inflation * total_tokens / era_per_year

如果最大獎勵減去實際獎勵還有剩餘獎勵,將剩餘獎勵收歸國庫,用於支持生態發展支出

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%。

上圖中,橫坐標為抵押率,藍色線縱坐標為年通脹率,綠色線縱坐標為年化收益率。

從圖中可以看出:

當抵押率小於50%,年化收益率大於20%,這會鼓勵用戶抵押DOT;當抵押率等於50%,年化收益率等於20%,符合設計預期;當抵押率大於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.individual.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模塊中定義了三種違規行為:

UnresponsivenessOffence (無響應)GrandpaEquivocationOffence (重複簽名,voting)BabeEquivocationOffence (重複出塊)

如果驗證人因任何一項違規行為被舉報,他們將被從驗證人節點池中移除(也就是凍結),並且在他們移除的時候不會得到獎勵。他們將立即被視為不活躍的驗證人,並將失去提名人。他們需要重新發布驗證意圖和收集提名人的支持。

跨Era的Slash懲罰在NPoS 中計算有三大難點:

一個提名人可以提名多個驗證人,並通過其中任何一個被slash 。在被slash 之前,這些stake 質押會被一個era 一個era 的重複使用。連續為E 個era 提名N 個代幣並不意味著你有N*E 個代幣用於slash,你還是只有N 個代幣。可懲罰的違規行為還可以在事情發生後才被發現,且不是按照順序的。

為了平衡以上的幾點,我們只對參與者在某個時間段內可以收到的最大懲罰進行懲罰,而不是所有懲罰的加總。這樣可以防止過度slash。同樣地,計算最大slash 的時間跨度是有限的,驗證人在slash 事件後被凍結並撤回提名,如前一節所述。這可以防止怒退(rage-quit)攻擊,在這種攻擊中,一旦被發現行為不端,參與者故意行為不端的程度會更高,因為反正他們的slash 數額已經達到最大值。

References

https://wiki.polkadot.network/docs/en/learn-stakinghttps://research.web3.foundation/en/latest/polkadot/Token%20Economics.htmlhttps://wiki.polkadot.network/docs/en/ learn-phragmenhttps://wiki.polkadot.network/docs/en/maintain-guides-how-to-validate-polkadot波卡的NPoS 機制是如何運作的?