ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Substrate pallet-staking 深度解析:NPoS 质押模块的角色模型、收益分配与惩罚机制

Substrate pallet-staking 深度解析:NPoS 质押模块的角色模型、收益分配与惩罚机制 区块链开发框架后端【免费下载链接】substrateSubstrate: The platform for blockchain innovators项目地址https://gitcode.com/gh_mirrors/su/substrate点击查看免费下载导读本文以 Substrate 仓库中的 frame/staking/README.md 为骨架结合 pallet-staking 源码 深入剖析 FRAME Staking 模块的设计与实现。Staking 模块是 Substrate 网络维护者Validator/Authority的产生机制网络依据自愿锁定资金的多少选出维护者正常履行职责者获得奖励失职者则面临资金被削减Slash的风险。读完本文你将掌握 Stash/Controller 账户体系、Validator/Nominator/Idle 三种角色流转、Era 与 Session 的关系、收益计算公式、Slash 惩罚流程以及 Phragmén 选举算法并能在自己的 Runtime 中正确配置与调用该模块。模块定位如何选出网络维护者在 Substrate 的 NPoSNominated Proof-of-Stake体系中Staking 模块是由谁来维护网络这一核心问题的答案。它基于**自愿锁定资金under deposit**的原则从一群自我举荐的账户中选出一组网络维护者正常运行期间这些资金会获得奖励但一旦被认定未妥善履行职责资金将面临Slash罚没。这一机制在代码中的直接体现是 frame/staking/Cargo.toml 所声明的pallet-stakingcrate其仓库文档即 frame/staking/README.md。关键术语Staking质押将资金锁定一段时间并承担 Slash 风险以成为受奖励的网络维护者。Validating验证运行节点主动维护网络——生产区块或保证链的最终确定性。Nominating提名将质押资金投到一个或多个验证者身后共享其获得的奖励与惩罚。Stash account金库账户持有所有者用于质押的资金。Controller account控制器账户控制所有者质押资金的账户当前版本已弃用独立控制器见下文。Era时代由整数个 Session 组成是重新计算验证者集合及各验证者的活跃提名者集合并支付奖励的周期。Slash罚没通过减少质押者资金实现的惩罚。设计目标支持由冷钱包cold wallet控制的资金参与质押。在不中断实体角色的前提下随时追加或提取部分资金。在提名者、验证者、闲置三种角色之间以最小开销切换。质押参与者模型Stash 与 Controller几乎所有与 Staking 模块的交互都始于bonding绑定。要成为 bonded 状态需要将一个持有资金的Stash 账户资金在质押期间被冻结与一个活跃的Controller 账户负责发出资金使用指令配对。在 pallet/mod.rs 的bond调用中可以看到ensure_signed取得 stash 后模块在Bonded与Payee两个存储项中写入绑定关系并创建StakingLedger// frame/staking/src/pallet/mod.rs节选 pub fn bond( origin: OriginForT, #[pallet::compact] value: BalanceOfT, payee: RewardDestinationT::AccountId, ) - DispatchResult { let stash ensure_signed(origin)?; // Controller 账户已弃用默认与 stash 相同 let controller_to_be_deprecated stash.clone(); ... BondedT::insert(stash, stash); PayeeT::insert(stash, payee); ... }注意源码中let controller_to_be_deprecated stash.clone();这一行——它印证了 README 中的说明Controller 账户正在被 Proxy 账户取代已无法为 stash 单独设置一个不同的控制器地址。set_controller调用目前的作用只是把控制器指回 stash 自身。StakingLedger定义于 lib.rs记录了每次质押的核心状态stash真正锁定资金、暴露于风险的账户total当前核算的总额等于active加上所有unlocking余额active将在后续轮次中继续处于风险中的金额unlocking正在解锁、最终可转出 stash 的金额队列FIFO新 era 的块追加在队尾以UnlockChunk { value, era }表示claimed_rewards该 staker 已领取奖励的 era 列表仅验证者维护。三种角色与状态流转任何已质押的账户对都处于三种角色之一Validator、Nominator或Idle对应StakerStatus枚举。模块提供三个对应的指令来切换角色调用目标角色说明validateValidator声明希望成为验证者候选nominateNominator声明提名一组验证者chillIdle暂时退出不再参与选举与投票Validating验证验证者负责验证区块或保障最终性因此必须避免恶意行为和离线。声明了验证意愿的 bonded 账户并不会立即当选它们只是成为candidate候选者可能在下一次era 选举中被选为验证者选举结果由提名者及其投票决定。从validate的源码pallet/mod.rs可以看到严格的资格检查ledger.active MinValidatorBond否则返回InsufficientBondprefs.commission MinCommission否则返回CommissionTooLow若MaxValidatorsCount已设置且当前验证者数已达上限则返回TooManyValidators。Nomination提名提名者不直接参与网络维护而是投票给一组验证者参与选举。提名意愿在下一轮选举生效提名者 stash 中的资金数额决定了其投票权重。验证者获得的奖励与惩罚都会按比例分摊给其提名者——这条规则从经济上激励提名者不要给作恶或离线的验证者投票否则自己也会损失资金。nominate的源码约束包括ledger.active MinNominatorBond、targets 非空且不超过NominationsQuota::get_quota(ledger.active)即由质押余额决定的提名配额并逐一检查目标必须是未设置blocked的验证者。Chilling退出任何角色都可以暂时休息chill提名者不再被视为投票者验证者也不再是下一轮选举的候选。chill由 controller 签名调用模块内部通过chill_stash清理对应存储。此外还有面向治理场景的chill_other当系统中验证者/提名者数量接近ChillThreshold阈值、或某提名者因MaxNominations配置调低而变得不可解码时任何人都可以调用它强制剔除不合格者。Era 与 Session 管理模块实现了SessionManagertrait这是查询新验证者集合以及在该 era 结束后对验证者集合发放奖励的唯一 API。相关机制包括SessionsPerEra配置决定一个 era 包含多少个 session每个 era 结束时验证者集合与每个验证者的活跃提名者集合被重新计算并支付奖励新的验证者列表会写入 Session 模块的Validators存储。在 pallet/mod.rs 中可以看到CurrentEra最新计划 era、ActiveEra当前正在发放奖励的 era其验证者集合必须等于SessionInterface::validators()等存储项以及Forcing枚举NotForcing/ForceNew/ForceNone/ForceAlways用于治理强制切换 era 的模式。SessionInterfacetrait 则封装了对pallet-session的交互禁用验证者、读取验证者列表、裁剪历史 session 数据。收益与惩罚Rewards and Slash收益与惩罚是 Staking 模块的核心目标是奖励良好行为惩罚不当行为或不可用性。奖励领取payout_stakers每个 era 的奖励必须在超过HISTORY_DEPTH由Config::HistoryDepth配置之前领取。payout_stakers可由任何账户调用源码中ensure_signed(origin)?后直接调用do_payout_stakers领取后同时支付给验证者及其提名者。为限制逐个修改提名者账户存储带来的 I/O 开销每个验证者只有MaxNominatorRewardedPerValidator个最大的质押者能够领取奖励——这正是ErasStakersClipped存储clipped Exposure存在的意义total与own字段保持不变但others只保留前 N 个最大的提名者。惩罚机制SlashSlash 可以在任何时候发生一旦不当行为被上报并经裁决将从验证者及其所有提名者的stash 账户中扣除相应金额。完整的 Slash 逻辑位于 slashing.rs其关键实现是StakingLedger::slashlib.rs采用**比例削减proportional slashing**策略若存在计划在slash_era BondingDuration及之后解锁的解锁块则在活跃余额与这些解锁块之间按比例分摊削减否则只削减活跃余额削减以偏好形式实施如果活跃余额加上部分更应被削减的解锁块不足以覆盖则继续消耗活跃余额与解锁块直至满足削减金额永远不会削减超过给定金额当某块变成粉尘dust时最后一块会被少削一点以补偿削减通过Config::OnStakingUpdate::on_slash通知监听者。模块还通过FilterHistoricalOffences过滤历史不当行为只处理发生在当前 bonding period 内的上报更早的上报会触发OldSlashingReportDiscarded事件并被丢弃lib.rs。治理层面cancel_deferred_slash可在SlashDeferDuration窗口内取消待执行的削减SlashRewardFraction配置上报者获得的举报赏金比例。Era 收益计算与分配Era 总奖励通胀曲线每个 era 的总收益由 inflation.rs 中的compute_total_payout计算。其依据是Config::RewardCurve即EraPayouttrait兼容旧的PiecewiseLinear曲线ConvertCurve定义的年度通胀模型staker_payout yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year remaining_payout max_yearly_inflation * total_tokens / era_per_year - staker_payout源码中的实际实现以毫秒计算portion era_duration / MILLISECONDS_PER_YEAR儒略年 365.25 天payout portion * yearly_inflation.calculate_for_fraction_times_denominator(...)maximum portion * (yearly_inflation.maximum * total_tokens)。剩余部分remaining_payout会发送到可配置端点Config::RewardRemainder通常流向国库。inflation.rs 的单元测试展示了一条典型曲线的参数min_inflation: 2.5%、max_inflation: 10%、ideal_stake: 50%、falloff: 5%即质押率达到 50% 时通胀最高、偏离理想质押率时通胀递减——该模型旨在激励网络趋向目标质押率。奖励点数reward_by_ids 与 EventHandlerera 总奖励在验证者及其提名者之间按奖励点数reward points分配。点数通过reward_by_ids/reward_by_indices累加其中reward_by_ids的定义见 lib.rs 的Pallet::reward_by_ids。同时模块实现了pallet_authorship::EventHandler每当区块生产者或被引用的叔块生产者出现时自动为其增加点数。每个 era 的点数记录在ErasRewardPoints存储中分配公式以individual / total为比例。验证者佣金与按质押比例分配验证者可以在ValidatorPrefs中声明一个commission佣金比例Perbill类型另含blocked字段表示是否接受新提名。每次支付时佣金先从验证者及其提名者的总奖励中扣除剩余部分按各自质押比例Exposure中的own/others除以total在验证者与其提名者之间分摊。注意比例分摊基于验证者背后的全部exposure而不仅是验证者与 top N 提名者的 exposure。奖励去向Payee所有获奖实体都可以通过set_payee选择奖励目的地RewardDestination枚举定义于 lib.rsStaked存入 stash 账户同时增加质押金额复利Stash存入 stash 账户不增加质押金额Controller存入 controller 账户显然不增加质押金额Account(AccountId)存入任意指定账户None不接收奖励。默认值为Staked。资金管理操作增加、解绑与再绑定已进入 stash 的资金支持以下操作调用签名要求作用bond_extrastash将 stash 自由余额中超出ledger.total的部分追加质押无金额上限unbondcontroller将部分或全部活跃资金调度为解锁并非立即可用需等待BondingDuration以 era 数计withdraw_unbondedcontroller解锁期结束后真正提取资金rebondcontroller将已调度解锁的资金重新绑定回活跃质押reap_stash任何人当 stash 总余额或 ledger 总额低于 Existential Deposit 时清理其全部质押数据结构unbond的源码实现值得注意当解锁块数量已达到MaxUnlockingChunks上限时模块会先自动调用do_withdraw_unbonded清理已到期的块再尝试调度新的解锁若仍无空位则返回NoMoreChunks。为避免质押系统残留粉尘unbond在活跃余额低于minimum_balance时会把它一并加入解锁金额并清零。此外若调用后活跃余额低于MinNominatorBond/MinValidatorBond且账户仍处于相应角色会返回InsufficientBond提示先chill。选举算法Phragmén当前选举算法基于Phragmén 方法实现参考实现见 w3f 的 NPoS 项目NPoS 即 Nominated Proof-of-Stake 的标准选举方案。算法不仅选出拥有最多质押价值与投票的验证者还试图将提名者的票在候选人之间均衡分配。为进一步保证均衡性可以应用可选的后处理步骤迭代归一化提名者的质押值直到某个提名者各票之间的总差异小于阈值。在代码层面选举由Config::ElectionProvider/GenesisElectionProvider抽象staking 自身实现ElectionDataProvider提供候选人与投票数据VoterList如pallet-bags-list维护按权重排序的投票者列表TargetList维护可被提名的目标及其被认可质押。选举结果的胜者数量由选举提供者的MaxWinners决定并与ValidatorCount保持一致。Config trait 与关键关联类型在 Runtime 中接入该模块需要实现 pallet/mod.rs 中的Configtrait核心关联类型包括关联类型/常量作用Currency/CurrencyBalance质押所依托的货币及余额类型须实现LockableCurrencyUnixTime计算 era 时长毫秒ElectionProvider/GenesisElectionProvider常规与创世时的选举提供者NominationsQuota依据质押余额决定每个提名者可提名的人数上限HistoryDepth历史保留的 era 数claimed_rewards等存储的上界RewardRemainder每 era 剩余奖励的去向端点Slash/Reward削减与奖励时的OnUnbalanced处理器通常调整总发行量SessionsPerEra每个 era 包含的 session 数BondingDuration解绑后资金需锁定的 era 数SlashDeferDuration削减延迟生效的 era 数须小于BondingDurationAdminOrigin可取消延迟削减、设置最低佣金的管理来源MaxNominatorRewardedPerValidator每个验证者可领取奖励的最大提名者数量MaxUnlockingChunksStakingLedger.unlocking的最大块数VoterList/TargetListNPoS 选举的投票者/目标排序列表提供者EventListeners接收 staking 更新事件当前仅上报 slashingintegrity_testpallet/mod.rs会在运行时构建时校验这些配置的一致性例如SlashDeferDuration BondingDuration、两个 ElectionProvider 的MaxWinners一致、MaxNominations与MaxVotesPerVoter相等且大于 1。GenesisConfig创世质押者GenesisConfig是可选的允许在链创世时预设一批初始质押者。其字段源码见 pallet/mod.rs为validator_count理想的活跃验证者数量minimum_validator_count触发紧急条件前的最小参与人数invulnerables不可被 Slash 或强制移除的验证者仅用于测试网预期不超过 4 个force_era初始 era 强制模式slash_reward_fraction举报者获得的削减比例canceled_payout因特殊原因取消削减后给予举报者的补偿金额stakers(stash, controller, balance, StakerStatus)列表创世时依次调用bond、validate/nominate建立初始质押者min_nominator_bond/min_validator_bond两类角色的最低活跃质押max_validator_count/max_nominator_count两类角色的人数上限None表示不限制。genesis_build会断言每个 stash 的可用余额足够质押且创世结束后VoterList::count() Nominators::count() Validators::count()确保所有创世 staker 都被正确纳入排序列表。代码实战在自定义 Pallet 中奖励验证者以下示例展示了如何在自己的 pallet 中调用 staking 模块的reward_by_ids来源于 README.md 的 Usage 章节与 lib.rs 中的实现一一对应use pallet_staking::{self as staking}; #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct PalletT(_); #[pallet::config] pub trait Config: frame_system::Config staking::Config {} #[pallet::call] implT: Config PalletT { /// Reward a validator. #[pallet::weight(0)] pub fn reward_myself(origin: OriginForT) - DispatchResult { let reported ensure_signed(origin)?; staking::PalletT::reward_by_ids(vec![(reported, 10)]); Ok(()) } } }要点自定义 pallet 的Config必须同时继承frame_system::Config与staking::Config才能通过staking::PalletT访问其公开函数reward_by_ids接收Vec(AccountId, u32)形式的(验证者, 点数)列表直接把点数累加到该 era 的ErasRewardPoints该函数由模块内部如pallet-authorship的事件回调与外部 pallet 共同使用是奖励点数注入的唯一入口。相关模块与生态Balancespallet-balances管理质押所依托的余额提供LockableCurrency能力见 frame/balances。Sessionpallet-session管理 session并在每个 era 结束时保存新的验证者列表见 frame/session。Authorshippallet-authorship向 Staking 模块回调奖励点数。Bags-listpallet-bags-list作为VoterList提供者按质押权重维护排序的投票者列表。Election-provider-support选举实现的支撑框架见 frame/election-provider-support。Staking Reward Curveframe/staking/reward-curve以过程宏build!定义年度通胀的PiecewiseLinear曲线。测试与验证模块的单元测试位于 frame/staking/src/tests.rs覆盖了上述全部核心路径例如bond_extra_works验证追加质押对StakingLedger的正确更新payout_stakers_handles_basic_errors验证 payout 调用在各种错误输入下返回一致消耗的权重payout_stakers_handles_weight_refund验证按被奖励提名者数量精确计算并退还权重inflation.rs 中的npos_curve_is_sensible测试则对通胀曲线的年化/日化/小时化输出做了数值断言。这些测试用例是理解模块行为边界的绝佳起点也是自定义 Runtime 集成时最可靠的参考依据。总结Substrate 的 Staking 模块通过质押换角色、era 驱动选举、点数分配收益、比例削减惩罚这一套闭环设计将经济激励与网络安全深度绑定。无论是接入 NPoS 共识的 Runtime 开发者还是研究链上治理与经济模型的读者理解 frame/staking/src/pallet/mod.rs 中的Config约束、lib.rs 中的账本与削减实现、inflation.rs 中的收益曲线都是掌握 Substrate 质押体系的关键一步。本文所述参数均以当前仓库源码为准实际接入时请以你自己的 Runtime 配置如SessionsPerEra、BondingDuration、MaxNominatorRewardedPerValidator作为最终依据。赞分享区块链开发框架后端【免费下载链接】substrateSubstrate: The platform for blockchain innovators项目地址https://gitcode.com/gh_mirrors/su/substrate点击查看免费下载相关推荐Substrate Root Offences Pallet 源码解析用根权限直接触发质押惩罚Slash的测试利器Substrate Root Offences Pallet 源码解析用根权限直接触发质押惩罚Slash的测试利器 导读 frame/root offen区块链开发框架后端Substrate 中 GRANDPA 终结论 Runtime 模块pallet-grandpa深度解析Substrate 中 GRANDPA 终结论 Runtime 模块pallet grandpa深度解析 本文基于仓库 frame/grandpa/READ区块链开发框架后端Initia权益质押计划代币经济与激励机制深度分析Initia权益质押计划代币经济与激励机制深度分析 引言构建跨链生态的质押新范式 在区块链技术快速演进的今天权益证明Proof of Stake机制已创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表