45 个版本 (主要破坏性)

34.0.0 2024年7月18日
33.0.0 2024年7月12日
32.0.0 2024年6月24日
31.0.0 2024年5月23日
0.0.0 2022年11月21日

#8 in #奖励

Download history 683/week @ 2024-04-27 454/week @ 2024-05-04 591/week @ 2024-05-11 1108/week @ 2024-05-18 893/week @ 2024-05-25 970/week @ 2024-06-01 1020/week @ 2024-06-08 631/week @ 2024-06-15 1273/week @ 2024-06-22 741/week @ 2024-06-29 569/week @ 2024-07-06 1384/week @ 2024-07-13 748/week @ 2024-07-20 1144/week @ 2024-07-27 819/week @ 2024-08-03 999/week @ 2024-08-10

每月下载 3,862
15 包中使用 15(直接)

Apache-2.0

3MB
54K SLoC

用于质押代委托的提名池

一个允许成员将他们的质押委托给提名池的托盘。提名池作为提名者,代表成员提名验证者。

索引

关键术语

  • pool id:每个池的唯一标识符。设置为 u32。
  • bonded pool:跟踪活跃质押资金的分配。请参阅 BondedPoolBondedPoolInner
  • reward pool:跟踪活跃质押资金所获得的奖励。请参阅 RewardPoolRewardPools
  • 解绑子池:解绑生命周期不同阶段的池集合。参见 SubPoolsSubPoolsStorage
  • 成员:池的成员账户。参见 PoolMemberPoolMembers
  • 角色:每个池的管理角色,可以控制提名和池的状态。
  • 点数:池资金成员部分的一个度量单位。点数最初与余额的比例为1(由POINTS_TO_BALANCE_INIT_RATIO设置),但随着惩罚的发生,这可能会改变。
  • 驱逐:池管理员强制驱逐成员的行为。
  • 已质押账户:从池ID派生出的无密钥账户ID,充当质押账户。此账户在质押系统中注册为提名人,并严格遵循与普通提名人相同的规则和条件。其质押额随成员加入而增加或减少,它可以nominatechill,并且如果未提名适当的验证者,可能甚至无法获得质押奖励。
  • 奖励账户:一个类似的无需密钥的账户,被设置为所有质押奖励的Payee账户。
  • 变更率:池佣金可以更改的比率。变更率由一个max_increase和一个min_delay组成,规定每块数可以应用的最大百分比佣金增加。
  • 节流:如果尝试的更改超出了变更率范围,则尝试增加佣金会被节流。

使用方法

加入

账户可以通过调用 Call::join 使用提名池进行质押。

领取奖励

加入池后,成员可以通过调用 Call::claim_payout 来领取奖励。

池成员还可以通过调用 Call::set_claim_permission 设置ClaimPermission,允许其他成员通过调用 Call::bond_extra_otherCall::claim_payout_other 分别无权限地质押或提取他们的奖励。

有关设计文档,请参阅奖励池部分。

离开

为了离开,成员必须采取两个步骤。

首先,他们必须调用 Call::unbond。解绑外接程序将通过解绑所有或部分成员资金开始解绑过程。

成员可以拥有最多 Config::MaxUnbonding 个不同的活跃解绑请求。

其次,一旦 sp_staking::StakingInterface::bonding_duration 轮次过去,成员可以调用 Call::withdraw_unbonded 来提取任何可用的资金。

有关设计文档,请参阅质押池解绑子池部分。

惩罚

惩罚在从惩罚时代+1到惩罚应用时代的质押池和解绑池之间均匀分配。因此,任何以下成员都会受到影响

  1. 解绑,或
  2. 在上述时代的范围内积极质押的成员将受到惩罚。成员的惩罚将基于其相对于总惩罚金额的股份比例。

惩罚不会改变任何单个成员的余额。相反,惩罚将仅减少与特定池相关的余额。但是,我们从不因为惩罚而改变池的总点数。因此,当发生惩罚时,池中点数与余额的比率会发生变化。换句话说,一个点的价值,最初是1比1与余额单位相对应,现在由于惩罚而小于一个余额。

管理

可以使用Call::create 调用来创建池。一旦创建,池的提名者或root用户必须调用 Call::nominate 来开始提名。可以随时调用 Call::nominate 来更新验证者选择。

Call::nominate 类似,Call::chill 将冷却到质押系统中的池中,而 Call::pool_withdraw_unbonded 将提取池质押账户的任何解绑块。后者调用是无权限的,可以由任何人随时调用。

为了帮助简化池管理,池有三个状态之一(见PoolState

  • 开放:任何人都可以加入池,且任何成员都不能被无权限移除。
  • 锁定:没有人可以加入,某些管理员角色可以踢出成员。踢出不是瞬时的,遵循与 unbond 和然后 withdraw_unbonded 相同的过程。换句话说,管理员可以无权限地解绑其他成员。
  • 销毁:不允许新成员加入,所有成员可以通过 Call::unbondCall::withdraw_unbonded 无权限地被移除。一旦池处于销毁状态,就不能再回滚到其他状态。

一个池有4个管理角色(参见 PoolRoles

  • 存款人:创建池并是初始成员。只有当所有其他成员都已离开时,他们才能离开池。一旦他们完全提取了资金,池就会被销毁。
  • 提名者:可以选择池提名的验证者。
  • 弹跳者:可以更改池的状态,如果池被阻止,还可以踢出成员。
  • 根:可以更改提名者、弹跳者或自己,管理并索要佣金,可以执行提名者或弹跳者可以执行的所有操作。

### 佣金

池可以设置佣金配置,通过 root 角色,使用 Call::set_commission 设置,并通过 Call::claim_commission 索要。必须提供收款账户和期望的佣金百分比。除了佣金本身之外,池还可以有最大佣金和变动率。

重要的是,一旦设置,最大佣金 Call::set_commission_max 和变动率 Call::set_commission_change_rate 就不能被移除,只能在后续更新中设置为更严格的值(即更低的最高佣金或更慢的变动率)。

如果设置,池的佣金将绑定到在应用待奖励时的 GlobalMaxCommissionGlobalMaxCommission 只能通过治理来更新。

当池被解散时,任何未索要的待处理佣金将转移到存款人。

实现说明:佣金类似于池的一个独立成员账户,有自己的奖励计数器,形式为 current_pending_commission

关键的是,佣金是根据在奖励转移到奖励池时的当前佣金来应用的。这是为了防止在积累奖励后更改佣金率到一个非常高的值,从而索要意外高的奖励部分。

拆除

如前所述,一旦池被销毁

  1. 首先,所有成员需要完全解绑并提取。如果池的状态被设置为 Destroying,这可以无权限地发生。
  2. 存款人自己完全解绑并提取。

请注意,在这一点上,根据质押系统的要求,池的质押账户的质押可能无法低于某个阈值作为提名者。在这种情况下,池应该让自身chill以允许存款人离开。请参阅Call::chill

实现者指南

希望实施此组件的钱包/应用应该注意的一些说明和常见错误

池成员

  • 一般来说,当池成员更改其总点数时,链将自动为他们索要所有待定奖励。这不是可选的,并且必须发生,以确保奖励计算正确(例如,请参阅bond的文档)。因此,请确保您警告用户这一点。如果他们看到额外质押了100 DOT,而他们5.23 DOT的待定奖励突然消失了,他们可能会感到惊讶。它并没有消失,它已经支付给您了!
  • 加入池意味着将资金转移到池账户。因此,根据您使用的钱包,您可能不再在“自由余额”部分看到转移到池中的资金。请确保用户了解这一点,并不要对此感到惊讶。此外,这里发生的转账已配置为永远不会意外破坏发送者账户。因此,为了加入池,您的发送者账户必须保持活跃,且至少有1 DOT。这意味着,有1 DOT作为生存存款,有1 DOT作为加入池的最小金额,您至少需要2 DOT才能加入池。因此,如果您建议成员用“最大可能价值”加入池,您必须从发送者账户中减去1 DOT,以避免意外将其杀死。
  • 点和余额不是同一回事!任何池成员在任何时间点都可以在质押池或任何解质押池中有点数。关键的事实是,在这些池中的任何一个池中,点数与余额的比例都不同,可能不是1。每个池都从1的比例开始,但随着时间的推移,由于诸如减记等因素,比例会被打破。随着时间的推移,在质押池中的100点可能价值90 DOT。请确保您要么将点数作为点数(而不是DOT)表示,要么更好的是,始终显示两者:“您在池y中有x点,价值z DOT”。请参阅此处和此处了解如何计算每个池的点数与余额比例的示例(几乎是微不足道的;)

池管理

  • 从系统其余部分的视角来看,池被视为一个单一的提名者。因此,此提名者必须始终遵守staking.minNominatorBond限制。与普通提名者类似,在完全解质押之前必须先chill,池也必须这样做。只有当存款人想要离开并解散池时,池的质押账户才会完全解质押。所有这些话总结起来就是:存款人只能在池chill之后才能离开链。

设计

说明:本节使用伪代码来解释一般设计,并不一定反映确切的实现。此外,假定对pallet-staking的API有基本了解。

目标

  • 通过维护减记事件的一致性,对在池中支持被减记验证器的成员进行充分惩罚,以维护网络安全性。
  • 最大化成员数量方面的可扩展性。

为了保持可扩展性,所有操作都与成员数量无关。为此,委托特定信息存储在成员本地,而池数据结构具有有界的数据。

质押池

抵押池以其总余额提名,不包括已提取用于解抵押的部分。抵押池的总点数始终等于委托成员点数之和。抵押池跟踪其点数并读取其抵押余额。

当成员加入池时,amount_transferred从成员账户转移到抵押池账户。然后池调用staking::bond_extra(amount_transferred)并发行新的点数,这些点数由成员跟踪并添加到抵押池的点数中。

当池已有一些余额时,我们希望转移前后的点数价值相等。因此,当成员以给定的amount_transferred加入抵押池时,我们保持抵押余额与点数的比例,使得

balance_after_transfer / points_after_transfer == balance_before_transfer / points_before_transfer;

为了实现这一点,我们根据以下方式发行点数

points_issued = (points_before_transfer / balance_before_transfer) * amount_transferred;

对于新的抵押池,我们可以任意设置每平衡单位发行的点数。在本实现中,我们使用1点对1余额的比例创建池(参见POINTS_TO_BALANCE_INIT_RATIO)。

相关外部调用

奖励池

当池首次抵押时,它会设置一个确定性、不可访问的账户作为其奖励目的地。这个奖励账户与RewardPool组合成一个奖励池。

奖励池与抵押池是完全独立的实体。除了其账户外,奖励池还跟踪其未领取和已领取的奖励作为计数器,以及待处理和已领取的佣金。这些计数器通过RewardPool::update_records更新。池的当前奖励计数器(总未领取的奖励,以点数计)也可以通过RewardPool::current_reward_counter方法调用。

有关奖励池机制的深入解释,请参见此链接

相关外部调用

解抵押子池

当成员解抵押时,其余额在抵押池账户中解抵押,并在与活跃时代关联的解抵押池中跟踪。如果不存在此类池,则创建一个。为了跟踪成员属于哪个解抵押子池,成员跟踪其unbonding_era

当成员启动解抵押时,其抵押池的索赔(balance_to_unbond)计算如下

balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * member.points;

如果是将任意数量的点数转移到解抵押池的第一个转移,则可以按余额发行点数。在本实现中,解抵押池以1点对1余额的比例初始化(参见POINTS_TO_BALANCE_INIT_RATIO)。否则,解抵押池持有与抵押池相同的点数与余额的比例属性,因此解抵押池中的成员点数根据以下方式发行

new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond;

为了可扩展性,解抵押子池的数量受到限制(参见TotalUnbondingPools)。一旦解抵押池的创建时间早于current_era - TotalUnbondingPools,该解抵押池将被删除。解抵押池将合并到解抵押池中,

unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance;
unbounded_pool.points = unbounded_pool.points + unbonding_pool.points;

此方案“平均”了解抵押池中的点数价值。

当成员的 unbonding_era 老于 current_era - [sp_staking::StakingInterface::bonding_duration] 时,它可以从对应的解绑池中提取积分。如果它的 unbonding_era 老于 current_era - TotalUnbondingPools,它可以从解绑池中提取积分。

相关外部调用

惩罚

本节假设惩罚计算由 pallet_staking::StakingLedger::slash 执行,该函数通过 sp_staking::OnStakingUpdate::on_slash 将信息传递给该组件。

解绑池需要被惩罚以确保所有在解绑池中而支持了作伪验证者的提名者受到惩罚。如果没有这些措施,成员在验证者作伪后可以立即解绑而没有任何后果。

这种策略对在惩罚之后加入的成员来说是不公平的,因为他们也会被惩罚,但解绑的成员却不受惩罚。后者对安全性来说更重要:如果池的验证者正在攻击网络,他们的成员需要快速解绑!避免惩罚会给他们提供激励,如果验证者被反复惩罚,他们就会这样做。

为了对加入者公平,这种实现还需要加入池,这些池正在积极质押,除了解绑池。为了维护简单,这些尚未实现。相关:https://github.com/paritytech/substrate/issues/10860

局限性

  • PoolMembers 不能用他们的质押资金投票,因为它们被转移到池的账户中。在未来,这可以通过允许成员通过投票分割使用他们的质押资金来解决。
  • PoolMembers 如果不喜欢提名,不能快速转移到另一个池,而必须等待解绑期限。

依赖项

~17–32MB
~531K SLoC