来源 | pintail.xyz
摘要:Altair 升级对个人验证者奖励有哪些影响?奖励和惩罚都进行了哪些改革?
从 Brandenburg an der Havel 向西看的夜空,箭头指向的是 Altair (牵牛星)
概要
- Altair 升级为轻客户端功能引入同步委员会,并对验证者奖励和惩罚进行了改革,这将对验证者的收益情况产生影响
- 总奖励的变化幅度变大;这对个人验证者的影响尤为明显
- Altair 引入了对滞后证明更严厉的惩罚,这将导致奖励轻微减少
- 在主网上导致滞后打包证明的事件,对奖励的影响将比现在更大
- 我们建议验证者密切关注证明的滞后情况;在 Altair 升级之后,对于那些证明打包经常滞后的验证者,他们的收益可能会大大减少
介绍
在上一篇文章,我们分析了以太坊信标链 (现在一般被称为“共识层”) 上的验证者在现实中的表现。信标链自 2020 年 12 月创世以来一直运行顺利 (只发生过几次小插曲),很多人都将注意力放在伦敦硬分叉 (包括得到广泛讨论的 EIP-1559 费用市场变更) 和即将到来的 eth1+eth2 合并上,届时以太坊的执行层将转为使用信标链来达成共识,这意味着 PoW 挖矿时代的结束。
同时,尽管关注度没那么高,共识层客户端的开发者一直专注于信标链的第一次升级——Altair 升级。这次分叉将引入轻客户端功能,并将作为以太坊权益证明共识机制协调过程的第一次排练。Altair 升级规范吸取了自信标链创世以来的一些经验教训,改善了它的激励结构和性能,部分通过改变奖励和惩罚的分配方式来实现,因此将对验证者奖励造成一定程度的影响。
在这篇文章,我们将主要研究 Altair 升级将带来的经济上的变更。我们将尝试了解对验证者可能产生的影响,通过使用主网的数据 (和一些假设),看看假如 Altair 升级在信标链创世时就启动了,验证者奖励会有什么不同。这将有助于验证者了解,继 Pymont 和 Prater 测试网升级后,主网进行 Altair 升级后可以有哪些期待。自从发现了在 Prater 测试网上的问题后,主网升级预计将在 10 月中旬左右启动。
奖励方案变更
第一个需要了解的变更是,在 Altair 升级下,基础奖励 (basic reward) 的含义略有不同。基础奖励指的是每个 epoch 分配到的奖励的基本单位。之前,每履行了四项验证者职责 (来源检查点投票、目标检查点投票、区块链头投票和打包区块提议) 中的一项就可以最多获得一整份基础奖励。但是,在 Altair 升级下,我们重新定义了基础奖励,它变成了一个完美验证者在履行所有职责时,每个 epoch 所获得的长期平均奖励。我们保持最高发放量不变,但验证者不是收到几倍的基础奖励,而是他们每个职责在基础奖励的比例。
奖励权重
除了重新定义“基础奖励”外,分配给多个职责的比例,和职责本身也有变化。下面的图表显示了在假设完美验证者表现的情况下,“之前”和“之后”的分配权重。
奖励分配图 [以下为代码]
import matplotlib.pyplot as plt head = 1 source = 1 target = 1 delay = 7/8 proposer = 1/8 fig, (ax1,ax2) = plt.subplots(1,2,figsize=(16,10)) ax1.pie( [head, source, target, delay, proposer], labels=['head', 'source', 'target', 'delay', 'proposer'], autopct='%1.1f%%' ) ax1.set_title( "Pre-Altair Reward Weights for Validator Duties" ) HEAD_WEIGHT = 14 SOURCE_WEIGHT = 14 TARGET_WEIGHT = 26 SYNC_WEIGHT = 2 PROPOSER_WEIGHT = 8 WEIGHT_DENOMINATOR = 64 ax2.pie( [HEAD_WEIGHT, SOURCE_WEIGHT, TARGET_WEIGHT, SYNC_WEIGHT, PROPOSER_WEIGHT], labels=['head', 'source', 'target', 'sync', 'proposer'], autopct='%1.1f%%' ) ax2.set_title( "Altair Reward Weights for Validator Duties" ) plt.show()
提议者和滞后奖励
在上面的图表中,要注意的第一项变化是提议者奖励提升至原来的 4 倍。你可能还记得,在 Altair 之前的规范中,我们有四项相等的证明奖励,但第四项奖励是在证明验证者和区块提议者间分的,其中证明验证者会得到奖励的 7/8,与滞后的打包时间成反比增减,而区块提议者会获得奖励的 1/8。如 Danny Ryan 在信标链创世后不久指出,区块提议者分到如此低比例的验证者奖励从来都不是研究者的本意,它明显是该规范的一个漏洞。这个错误在 Altair 规范里得到了修正,区块提议者将如一开始计划般分得总奖励的 1/8,而不是在 Altair 前的规范里的 1/4 总奖励的 1/8。
同时,“滞后”奖励 (delay reward) 会被完全移除。相反,其他证明奖励 (区块链头、来源检查点和目标检查点) 被赋予了不同的打包期限:
- 正确的区块链头投票只有在下一个 slot 里被打包才会获得奖励
- 正确的来源检查点投票只有在 5 个 slot 内被打包才会获得奖励 (即
integer_squareroot(EPOCH_LENGTH)
) - 正确的目标检查点只有在 32 个 slot 内被打包才会获得奖励 (即
EPOCH_LENGTH
)
这很好地以合乎逻辑的方式奖励及时的证明投票。特别是,区块链头的投票只有在快速收到的情况下才能帮助网路在链头达成共识。目标检查点的投票只有在一个 epoch 内被打包才对网络有意义,因此验证者只有在它在 32 个 slot 内被打包才能因对目标检查点正确投票而得到奖励。来源检查点投票本身实际上并不有助于区块链达成共识 (而只有当有正确来源检查点投票的证明被打包了才起作用)。因此,来源检查点投票的奖励要在证明在 5 个 slot 内被打包才会被支付出去。这个期限—— integer_squareroot(EPOCH_LENGTH)
——选的是其他两项奖励在几何学上一半的时间。所以之前用于正确区块链头、目标检查点和来源检查点投票的分级奖励方案在某种程度上类似于了上一版本规范中的“滞后奖励”,即给证明更快被打包的验证者支付更多奖励。
最后,证明奖励的权重已经被改变了,与来源检查点和区块链头奖励一并从 16/64 变成 14/64,而目标检查点奖励从 16/64 上调至 26/64。 这样的调整反应的是这样的一个现实——正确的目标检查点投票是证明过程中最重要的部分。只要网络能在每个 epoch 的目标检查点上达成共识,这条链就仍然可以做最终敲定。
同步委员会
奖励方案的最后一个不同点在于给同步委员会新增了一项奖励。这实现了 Altair 升级引入的关键新功能,轻客户端就是通过它与网络同步数据的。同步委员由一组 512 名的验证者组成,它对每个信标链头签名。为了确保轻客户端能够在不保存整个信标链状态的情况下了解同步委员会的参与者有谁,同步委员会的轮换相对没那么频繁——周期为每 256 个 epoch 或大概 1 天 。
与区块提议的机制一样,同步委员会的成员是在验证者中随机选择的,他们在每个同步委员里的时长为 256 个 epoch。在这个过程中,那些验证者可以在他们所参与同步委员会的每个 slot 获得同步委员会的奖励。
计算同步委员会数量和预计每个验证者被选进委员会的次数 [以下为代码]
SECONDS_PER_SLOT = 12 SLOTS_PER_EPOCH = 32 EPOCHS_PER_COMMITTEE = 256 COMMITTEE_SIZE = 512 SECONDS_PER_YEAR = 31556952 seconds_per_committee = SECONDS_PER_SLOT * SLOTS_PER_EPOCH * EPOCHS_PER_COMMITTEE committees_per_year = SECONDS_PER_YEAR / seconds_per_committee print(f"{committees_per_year:.1f} sync committees are selected each year") NUM_VALIDATORS = 200000 expected_committee_selections_per_year = committees_per_year * COMMITTEE_SIZE / NUM_VALIDATORS print(f"with a validator set of {NUM_VALIDATORS} validators, on average each validator will be assigned " f"to a sync committee {expected_committee_selections_per_year:.2f} times per year")
输出:
每年会选出 321.0 个同步委员会
在一个有 200,000 个验证者的验证者集里,平均每个验证者每年被分配到一个同步委员会的次数是 0.82
与我们在之前关于信标链奖励的文章中分析提议职责变化的方法类似,我们可以用二项分布来分析一个验证者预计可以被选中参与同步委员会次数的变化差异。在下面的计算里,我们将假设验证者总人数为 200,000。
[以下为代码]
from scipy.stats import binom SECONDS_PER_YEAR = 31556952 SECONDS_PER_SLOT = 12 SLOTS_PER_EPOCH = 32 COMMITTEE_EPOCHS = 256 NUM_VALIDATORS = 200000 COMMITTEE_VALIDATORS = 512 epochs_per_year = SECONDS_PER_YEAR / (SECONDS_PER_SLOT * SLOTS_PER_EPOCH) x = [el for el in range(7)] p_selection = COMMITTEE_VALIDATORS / NUM_VALIDATORS committees_per_year = epochs_per_year / COMMITTEE_EPOCHS y = binom.pmf(x, committees_per_year, p_selection) fig, ax = plt.subplots(figsize=(12, 8)) ax.bar(x, y) ax.set_xlim(xmin=-0.5) ax.set_ylim(ymin=0) ax.set_title('Probability mass function (200,000 validators) — number of sync committees per year') ax.set_xlabel('Number of sync committee selections in a year') ax.set_ylabel('Proportion of validators') print(y[0])
输出:
0.4391785090173583
因此,在 200,000 个活跃验证者中,几乎有一半的验证者在一年中不会被选入一个信标委员会。如果验证者集的容量增加,被选中进入验证者委员会的概率将下降到更低。
给完美参与者建模
记住这点后,现在开始为假设所有验证者都完美履行他们职责的情况下,可能的年度可得奖励分布建模。回顾一下,在 Altair 升级前,即使在完美参与的情况下,也会因为提议者职责是随机分配的而在验证者间存在差异。在 Altair 升级后,区块提议的奖励值翻了 4 倍(从总奖励的 3.1% 上升到 12.5%),因此验证者奖励的差异也会相应扩大。同步委员会的引入是导致验证者奖励差异的一个新来源。
由于同步委员会奖励的获得是随机的,且独立于提议者奖励,我们可以通过以下方法计算年度总奖励的分布:计算一年中num_proposer_duties
(履行提议区块的次数) 和 num_sync_committees
(参与同步委员会的次数) 的每种可能组合,与每种分布的概率相乘,然后把奖励加起来。然后,我们可以把这个分布与描述 Altair 升级前验证者奖励差异的简单的二项分布进行对比。
给完美参与的年度奖励建模 [以下为代码]
import math import pandas as pd def get_quantile(pmf, quantile): cumulative = 0 for x, prob in sorted(pmf.items()): cumulative += prob if cumulative >= quantile: return x slots_per_year = SECONDS_PER_YEAR / SECONDS_PER_SLOT GWEI_PER_ETH = int(1e9) gwei_per_validator = 32 * GWEI_PER_ETH BASE_REWARD_FACTOR = 64 PREALTAIR_NUM_BASE_REWARDS = 4 PREALTAIR_PROPOSER_REWARD_QUOTIENT = 8 base_reward = gwei_per_validator * BASE_REWARD_FACTOR // math.isqrt(NUM_VALIDATORS * gwei_per_validator) total_reward = base_reward * NUM_VALIDATORS prior_proposer_share = base_reward // PREALTAIR_NUM_BASE_REWARDS // PREALTAIR_PROPOSER_REWARD_QUOTIENT prior_proposer_reward = prior_proposer_share * NUM_VALIDATORS // SLOTS_PER_EPOCH prior_att_reward = base_reward - prior_proposer_share altair_proposer_reward = total_reward * PROPOSER_WEIGHT // SLOTS_PER_EPOCH // WEIGHT_DENOMINATOR altair_att_reward = base_reward * (HEAD_WEIGHT + SOURCE_WEIGHT + TARGET_WEIGHT) // WEIGHT_DENOMINATOR sync_reward = total_reward * COMMITTEE_EPOCHS * SYNC_WEIGHT // COMMITTEE_VALIDATORS // WEIGHT_DENOMINATOR # distribution of committee selections per year n_committees = [el for el in range(11)] pmf_committees = binom.pmf(n_committees, committees_per_year, COMMITTEE_VALIDATORS / NUM_VALIDATORS) # distribution of block proposal opportunities per year n_proposals = [el for el in range(51)] pmf_proposals = binom.pmf(n_proposals, slots_per_year, 1 / NUM_VALIDATORS) n_bins = 32 bins = [1.7 + i / 40 for i in range(n_bins)] altair_hist = [0] * n_bins prior_hist = [0] * n_bins # calculate all possible reward levels (up to 50 block proposals) assuming perfect participation prior_pmf = {} for props in n_proposals: reward = props * prior_proposer_reward + epochs_per_year * prior_att_reward prior_pmf[reward] = pmf_proposals[props] # bin the rewards to generate histogram for reward_gwei, prob in prior_pmf.items(): reward = reward_gwei / GWEI_PER_ETH for i, edge in enumerate(bins[1:]): if reward < edge: prior_hist[i] += prob break prior_mean = sum([p * r / GWEI_PER_ETH for r, p in prior_pmf.items()]) prior_sigma = math.sqrt(sum([p * (r / GWEI_PER_ETH)**2 for r, p in prior_pmf.items()]) - prior_mean**2) prior_lq = get_quantile(prior_pmf, 0.25) / GWEI_PER_ETH prior_median = get_quantile(prior_pmf, 0.5) / GWEI_PER_ETH prior_uq = get_quantile(prior_pmf, 0.75) / GWEI_PER_ETH prior_iqr = prior_uq - prior_lq print('Pre-Altair annual reward statistics (ETH)') print('-----------------------------------------') print(f' median: {prior_median:.4f}') print(f' mean: {prior_mean:.4f}') print(f' standard deviation: {prior_sigma:.4f}') print(f'interquartile range: {prior_iqr:.4f}') #print(sum(prior_hist)) # check histogram sums to unity # calculate all possible reward levels (up to 50 block proposals and 10 committee selections) altair_pmf = {} for comms in n_committees: for props in n_proposals: reward = comms * sync_reward + props * altair_proposer_reward + epochs_per_year * altair_att_reward prob = pmf_committees[comms] * pmf_proposals[props] if reward in altair_pmf: altair_pmf[reward] += prob else: altair_pmf[reward] = prob # bin the rewards to generate histogram for reward_gwei, prob in altair_pmf.items(): reward = reward_gwei / GWEI_PER_ETH for i, edge in enumerate(bins[1:]): if reward < edge: altair_hist[i] += prob break altair_mean = sum([p * r / GWEI_PER_ETH for r, p in altair_pmf.items()]) altair_sigma = math.sqrt(sum([p * (r / GWEI_PER_ETH)**2 for r, p in altair_pmf.items()]) - altair_mean**2) altair_lq = get_quantile(altair_pmf, 0.25) / GWEI_PER_ETH altair_median = get_quantile(altair_pmf, 0.5) / GWEI_PER_ETH altair_uq = get_quantile(altair_pmf, 0.75) / GWEI_PER_ETH altair_iqr = altair_uq - altair_lq print('\nAltair annual reward statistics (ETH)') print('-------------------------------------') print(f' median: {altair_median:.4f}') print(f' mean: {altair_mean:.4f}') print(f' standard deviation: {altair_sigma:.4f}') print(f'interquartile range: {altair_iqr:.4f}') #print(sum(altair_hist)) # check histogram sums to unity print(f'\nrelative spread: {altair_sigma / prior_sigma:.1f} (standard deviation) / ' f'{altair_iqr / prior_iqr:.1f} (interquartile range)') fig, (ax1,ax2) = plt.subplots(2, 1, figsize=(12,10)) ax1.bar(bins, prior_hist, 1 / n_bins, align='edge') ax1.set_title('Pre-Altair Annual Rewards Distribution (200,000 validators, perfect participation)') ax1.set_ylabel('Proportion of validators') ax2.bar(bins, altair_hist, 1 / n_bins, align='edge') ax2.set_title('Altair Annual Rewards Distribution (200,000 validators, perfect participation)') ax2.set_xlabel('Annual reward (ETH)') ax2.set_ylabel('Proportion of validators') ax.set_title('Distribution of annual rewards assuming perfect performance') ax.set_xlabel('Annual reward (ETH)') ax.set_ylabel('Proportion of validators');
输出:
升级前的年度奖励数据 (ETH)
-----------------------------------------
中位数: 2.1031
平均数: 2.1038
标准差: 0.0181
四分位差: 0.0200
Altair升级后的年度奖励数据 (ETH)
-------------------------------------
中位数: 2.0951
平均数: 2.1038
标准差: 0.1025
四分位差: 0.1400
相对面积: 5.7倍 (标准差) / 7.0 倍 (四分位差)
如预期般,上面的统计数据和图表显示,在验证者完美参与的情况下,尽管 Altair 升级后的平均奖励发放量保持不变,但奖励的分布面积却明显增加了。同时,与 Altair 升级前的分布相比,Altair 升级下奖励标准差增加了 5.7 倍。
惩罚
Altair 升级中,奖励权重的变化与相应错过或错误投票的惩罚权重变化是相匹配。但比权重更重要的是处理滞后证明的方式。如果一个证明没有被打包在尽可能早的 slot 里,那么该验证者会被认为没有参与到证明的部分或全部内容,从而造成对那些滞后部分的惩罚。
这是一个重要的区别。试想一下,如果一个证明是正确的,但它“晚”了一个 slot 才被打包。在 Altair 升级前,这个证明会收到来源检查点、目标检查点和区块链头投票的最高奖励,和一半的“滞后”奖励,这些加起来接近于证明可得最高建立的 90%。但是,在 Altair 升级的规则下,验证者将被当作区块链头的投票是错误的来处理,并会接受惩罚。因此,当证明晚了一个 slot 被打包最多只能获得可得最高奖励的 48%。
在 Altair 升级下,因为证明做得太晚而无法获得来源检查点奖励 (即滞后超过 5 个 slot) 的情况,最好的结果是得到的总奖励为 0 (而且可能整体为负值),而 Altair 之前,相同的证明情况会获得高达最高奖励的 77%。简言之,在 Altair 下,对迟来证明的惩罚要严厉得多。在网络压力下,如常见的低参与率的测试网,即使是完全尽职的验证者也可能受到惩罚。
罚没和怠工惩罚
除了惩罚权重和滞后机制外,罚没和怠工溢金参数 (inactivity leak parameters) 也有一些变更。由于在本文对这两个因素都没有建模 (满足怠工溢金条件的情况还未在主网出现过,罚没也很罕见,且用户使用标准设置就能轻易避免),所以本文不涉及这两方面的探讨。关于更详细的内容,参见 Vitalik 的注释规范。
主网数据
为了更真实、直接地比较 Altair 升级前后的奖励方案,我们希望使用一些真实的数据。幸运的是,现有的主网数据已足够相似了,如果我们做一些假设,使用它们实际收到奖励的数据,我们可以比较在 Altair 方案下验证者会得到什么样的奖励。
方法轮
我们将使用信标链前 62,000 个 epoch 的数据 (与前一篇文章的数据集相同)。这意味着我们的数据覆盖了信标链前八个月,直到2021年 9 月 3 日的运行情况。然后,我们会计算每个 epoch 里给来源检查点、目标检查点和区块链头正确 (和及时) 投票的可得奖励。通过使用 chaind 提供的 epoch 数据总结,我们可以看到每个验证者是否正确投票了,以及证明的打包滞后了多少个 slot。我们可以使用这些信息,和 Altair 升级下的 base_reward
计算方式和权重,算出如果信标链创世时就使用 Altair 升级的方案,验证者得到的奖励和/或惩罚情况会是怎样的。
然后,我们需要模拟同步委员会,对每个验证者如果被选参与一次同步委员会的表现做出一些猜测。在这个分析里,每 256 个 epoch 会有 512 个验证者被随机选中,如果他们在该 epoch 证明成功,那么就假设他们在同步委员会的每个 epoch 的表现都是完美的,否则当没有被选中处理。
最后,我们可以使用之前计算出的提议者奖励。这些都是简单地乘以 4 算出提议者奖励的,它们在 Altair 方案下也会是这样算。这些步骤已经在一个 Python 脚本实现了,结果存在在 JSON 文件中。
假设
因此,为了进行这种比较,我们做了一些假设,例如:
- 提议区块是相同的,他们包含相同数量的证明,因此在 Altair 升级方案下,它的数值是之前规则下的 4 倍
- 在 Altair 升级下,证明的打包时间与在之前的规则下是一样的 (例如 epoch 处理的变化、客户端优化等,这些都可能改变打包速度,这些因素在此不作考虑)
到目前为止,最大的假设是:
- 如果验证者在一个 epoch 里提交了证明,就假设这样的每个同步委员会的参与者都成功履行了他们在整个 epoch 里的职责。
最后的一个假设是最不可靠的——我们假设验证者只要成功提交了证明,即使滞后了,也算作成功参与了整个 epoch (即 32 次)。那么,在某些情况下,这是对验证者表现的一个宽松假设 (即使它们提交了该 epoch 的一个证明,我们也不能确定他们在每个 slot 的同步委员会里都完美履行职责了)。另一方面,有些情况下,这会是一个过于苛刻的假设,因为在一个给定 epoch 证明失败的验证者仍然有可能成功参与了一些或全部 slot 里的同步委员会。
现在让我们看看数据
计算数据和绘制净收益的数据图表 [以下为代码]
import json with open('aggregate_rewards.json') as f: rewards = json.load(f) with open('check.json') as f: check = json.load(f) # we're using the "reduced genesis set" of validators as in the previous article with open('reduced_genesis_set.json') as f: reduced_genesis_set = json.load(f) altair_rewards = [] prior_rewards = [] for validator_index in reduced_genesis_set: altair_rewards += [rewards[str(validator_index)] / 1e9] prior_rewards += [check[str(validator_index)] / 1e9] df = pd.DataFrame({'altair_rewards': altair_rewards, 'prior_rewards': prior_rewards}) prior_iqr = df['prior_rewards'].quantile(0.75) - df['prior_rewards'].quantile(0.25) altair_iqr = df['altair_rewards'].quantile(0.75) - df['altair_rewards'].quantile(0.25) prior_mean = df["prior_rewards"].mean() altair_mean = df["altair_rewards"].mean() mean_diff = prior_mean - altair_mean print('Statistics — first 62,000 epochs (pre-Altair rewards scheme, measured in ETH)') print('-----------------------------------------------------------------------------') print(f' median: {df["prior_rewards"].quantile(0.5):.4f}') print(f' mean: {prior_mean:.4f}') print(f' standard deviation: {df["prior_rewards"].std():.4f}') print(f'interquartile range: {prior_iqr:.4f}') print('\nStatistics — first 62,000 epochs (Altair rewards scheme, measured in ETH)') print('-------------------------------------------------------------------------') print(f' median: {df["altair_rewards"].quantile(0.5):.4f}') print(f' mean: {altair_mean:.4f}') print(f' standard deviation: {df["altair_rewards"].std():.4f}') print(f'interquartile range: {altair_iqr:.4f}') print('\nComaprison') print('-----------') print(f'The mean per-validator reward under Altair changed by {100*(altair_mean / prior_mean - 1):.1f}%') print(f'The interquartile range (spread) of rewards under Altair was {altair_iqr / prior_iqr:.1f} times greater') fig, (ax1,ax2) = plt.subplots(2, 1, figsize=(12,10)) bins = [b/20 for b in range(50)] df['prior_rewards'].plot.hist(ax=ax1, bins=bins) df['altair_rewards'].plot.hist(ax=ax2, bins=bins) ax1.set_title("Net rewards — pre-Altair reward scheme (first 62,000 epochs)") ax1.set_ylabel("Number of validators") ax2.set_title("Net rewards — Altair reward scheme (first 62,000 epochs)") ax2.set_xlabel("Net reward (ETH)") ax2.set_ylabel("Number of validators");
输出:
统计数据— 前 62,000 个 epochs(Altair 升级前的奖励方案,以 ETH 计价)
-----------------------------------------------------------------------------
中位数: 2.1649
平均数: 2.1387
标准差: 0.1459
四分位差: 0.0551
统计数据— 前 62,000 个 epochs(Altair 升级后的奖励方案,以 ETH 计价)
-------------------------------------------------------------------------
中位数: 2.1241
平均数: 2.1144
标准差: 0.1775
四分位差: 0.1344
比较
-----------
在 Altair 方案下,平均每个验证者的奖励下降了 1.1%
在 Altair 方案下,奖励的四分位差 (面积分布) 提高了 2.4 倍
![png](https://i.ibb.co/GTKKSJv/net-reward2.png)
比较验证者分布
比较统计数据,Altair 的规则比之前的方案更严格,因为当它们被应用到数据集时,平均奖励下降了 1.1%。如前面所暗示的,这很可能是由于对滞后证明有更严厉惩罚造成的。
也如预测的那样,在 Altair 方案下,奖励的面积更广了。奇怪的是,尽管在上面的直方图里清晰可见,这个现象在比较标准差时并没有那么明显,在 Altair 方案下只略微上升了。这可能是受异常值影响 (例如,有些从不提交证明的验证者还受到严厉的惩罚)。但是,当与更稳健的四分位差比较时,我们看到 Altair 方案的面积是之前方案面积的两倍以上。这并不像我们对完美验证者建模所预测那样,面积增加 7 倍。这是因为在真实数据里,奖励差异不仅因为提议者/同步委员会参与者的分配是随机的,还因为验证者没有完美履行职责,导致 Altair 方案前的真实数据从一开始就比建模的完美数据有更大的差异,因此 Altair 升级带来的变化没有那么明显。
比较奖励发放量
在使用几乎横跨信标链自创世以来整个时期的数据时,我们应该记住存在这样一种可能——网络性能时随时间变化的。要想了解如果 Altair 升级在信标链历史上不同时间被激活会对奖励有什么样的影响,我们计算出在 Altair 方案下和 Altair 规则前每个 epoch 的总奖励发放量。发放量的相对变化如下图所示。
绘制每个 epoch 的相对发放量图表 [以下为代码]
with open('issuance.json') as f: issuance = json.load(f) delta = [] sum_old = sum_new = 0 for (old, new) in zip(issuance['old_issuance'], issuance['alt_issuance']): delta.append(100 * (new / old - 1)) print(f"Mean change in issuance: {sum(delta) / len(delta):.2f}%") print(f"Greatest per-epoch drop in issuance from Altair: {min(delta):.2f}%, greatest increase: {max(delta):.2f}%") fig, ax = plt.subplots(figsize=(12, 8)) ax.plot(pd.Series(delta).rolling(16).mean()) ax.set_title('Percentage Change in Issuance after Applying Altair Rewards Scheme (16-epoch moving average)') ax.set_xlabel('Epoch') ax.set_ylabel('% change');
输出:
发放量的平均变化lin: -1.05%
Altair 方案带来的每 epoch 最大 降幅: -82.47%, greatest increase 最大增幅: 1.38%
如上图所示,Altair 方案下的奖励平均比现状减少了 1% 左右。但是,在 2021 年 4 月丢失区块的事件里,奖励会减少得更多。在此次的事件里,占主导的信标链客户端停止产生区块,导致整个网络都滞后了证明提交,参与率下降了 84.8%。尽管这个事件对 Altair 前网络的奖励造成很小的影响,但如果当时已经在使用 Altair 方案的规则,影响明显时要大得多。程度轻一点的情况,同样的影响可以在创世前后观察到 (但是参与率也稍有下降),也可以观察 epoch 59000 前后的情况,对应于 2021 年 8 月的孤块事件。
之所以此类事件在 Altair 规则下后果会更严重,是因为如前文解释般,滞后证明在 Altair 下要接受更严重的惩罚。如下图所示。
绘制根据打包滞后变化的证明奖励图表 [[以下为代码]
pre_altair_reward = [] altair_reward = [] x = [] for delay in range(1,33): x += [delay] pre_altair_reward.append(0.75 + 0.25 * (7/8) / delay) if delay == 1: altair_reward.append((HEAD_WEIGHT + SOURCE_WEIGHT + TARGET_WEIGHT) / WEIGHT_DENOMINATOR) elif delay <= 5: altair_reward.append((SOURCE_WEIGHT + TARGET_WEIGHT - HEAD_WEIGHT) / WEIGHT_DENOMINATOR) else: altair_reward.append((TARGET_WEIGHT - HEAD_WEIGHT - SOURCE_WEIGHT) / WEIGHT_DENOMINATOR) fig, ax = plt.subplots(figsize=(12, 8)) ax.plot(x, pre_altair_reward, label="Pre-Altair") ax.plot(x, altair_reward, label="Altair") ax.set_xlabel("Inclusion Delay") ax.set_ylabel("Per Epoch Reward (as fraction of max issuance)") ax.set_title("Reward for Correct Attestation According to Inclusion Delay") ax.set_xlim([1,32]) leg = ax.legend()
总结
从我们最初的建模可以看出,把提议者的奖励提升到 4 倍,以及引入同步委员将造成奖励的差异比目前的情况更大。这种差异将对个人验证者带来比大型质押池 (它们的奖励将接近于平均水平) 更大的影响。在考虑任何未来可能影响验证者奖励结构的变更时 (例如以后引入分片),都应牢记这个影响。
此外,根据上文的分析,似乎 Altair 升级激活后,平均奖励可能会有所减少,尽管引入的变化在最大可得奖励方面是中立的。受这个影响最大的是那些经常滞后提交证明的验证者,可能是网路延迟造成的。因此,验证者最好在 Altair 升级激活前密切关注自己的表现。
特别是,参与度降低或出块失败会导致证明打包延迟,在 Altair 方案下验证者奖励很可能会下降得更明显。到目前为止,这种情况仅在信标链上发生过一次,但我们可以预期随着未来的更新 (尤其是合并和分片) 引入新的复杂性,这种事件在未来是可能发生的。
致谢
非常感谢 Lido finance 通过它们的生态系统资助计划对这项工作提供资金。一如既往地,感谢 Jim McDonald 提供 chaind 的数据,让这个分析得以完成,还要感谢 Barnabé Monnot 和 Vasiliy Shapovalov 的宝贵反馈意见。图片由 Mathias Krumbholz 提供,添加了箭头。
ECN的翻译工作旨在为中国以太坊社区传递优质资讯和学习资源,文章版权归原作者所有,转载须注明原文出处以及ethereum.cn,若需长期转载,请联系eth@ecn.co进行授权