Ξ

    Search by

    Staking:实践中的验证者奖励

    在实际运行中以太坊验证者的奖励情况分析


    p

    pintail       2021-08-16

    来源 | pintail.xyz

    翻译/校对 | 林晗/ECN


    cover

    评价验证者表现

    上一篇文章中,我们回顾了验证者在参与信标链共识过程所获得的奖励和惩罚,并基于验证者的网络参与率和正常运行时间,构建出了一个模型来估计预期的净收益。

    在本篇文章中,我们将对主网的实际运行数据进行查验,并将其与模型预估值相比较。为此,我们将使用由 Jim McDonald 设计的价值非凡的 chaind 工具对信标链进行索引。另外,为了避免2021 年 4 月下旬的意外暂停出块事件对研究结果产生影响,我们的研究范围主要聚焦于主网前 32,000个时段 (大约 4.5 个月) 的数据。


    对平均净收益进行建模

    首先,本研究将对来自主网的汇总数据展开查验。通过汇总超过 120,000 个验证者的数据,我们得以了解验证者所能获得的平均奖励或者惩罚值。在上一篇文章中,模型所估算出的数值都是预测 (即平均值) 值,基于此,我们能对模型预测值与实际运行结果之间的差异有比较清晰的认知。这将有助于我们对单个验证者以及其奖惩机制展开深入研究,同时也有助于我们准确地分析,究竟是哪类问题导致了最终实际运行结果的不佳。

    预期净收益模型公式如下:

    img

    我们对主网进行了整体建模,在我们的模型中,每个验证者的正常运行时间 U是呈线性的,因此,我们可以将U 等同于整体网络参与率 P,由此可得:

    img

    至此,我们的模型只剩下两个参数——参与率 P 和基本奖励 B,而后者又由全网活跃质押额所决定。为简单起见,我们假设参与率 P 在所考虑的时期内是恒定的,即为网络的平均值。在每一时段中,该数值P等于活跃余额(即所有活跃验证者的有效余额)除以总余额(即所有验证者的有效余额)。

    输入、常数、连接数据库

    代码如下:

    import time import math import statistics import csv from datetime import datetime from datetime import timedelta import psycopg2 import matplotlib.pyplot as plt import matplotlib.dates as mdates import pandas as pd FAR_FUTURE_EPOCH = 2**64 - 1 # as defined in spec END_EPOCH = 32000 # open/restart connection to chaind database try: cursor.close() connection.close() except: pass connection = psycopg2.connect(user="chain", host="127.0.0.1", database="chain", password="medalla") cursor = connection.cursor()

    找到平均数、参与率

    cursor.execute(f"SELECT f_active_balance, f_attesting_balance FROM t_epoch_summaries " f"WHERE f_epoch < {END_EPOCH} ORDER BY f_epoch") active_balance, attesting_balance = list(zip(*cursor.fetchall())) p_rate = [attesting_balance[i] / active_balance[i] for i in range(len(active_balance))] average_p_rate = sum(p_rate)/len(p_rate) print(f"average participation rate: {100*average_p_rate:.1f}%")

    output:
    
         average participation rate: 99.0%
    
    
    输出:
    
    ​	平均参与率:99%
    
    

    如上述计算,在前 32,000 个时段中,网络平均参与率为 99%。 有了这个数字,我们就可以根据每个时段活跃验证者质押的 Ether 数量对预期奖励进行建模估测。


    实际平均奖励

    为了进行比较,我们还需要计算实际的平均奖励值。这可以通过查验 chaind 记录的活跃验证者余额的变化来实现。不过,我们需要对汇总数据进行一些调整,以准确反映主网奖励和惩罚值:

    1.我们排除了错误质押的情况,用户错误地向活跃验证者进行eth1存款;

    2.我们排除了被罚没的验证者,因为罚没的惩罚金额相对较大,而且仅在同一验证者同时运行多个实例或行使可证明的恶意行为时才会发生;

    1. 我们排除了罚没他人的验证者,因为这些验证者获得的巨额奖励可能会扭曲整体奖励情况。

    如果排除罚没者和被罚没者,那么我们排除了大约多少比例的验证者呢?通过将被排除的验证者的数量 (即被罚没者的数量加上罚没者的数量) 除以活跃验证者数量的最小值 (比如说创世验证者的数量),可以简单粗暴地计算出这一比例的上限。

    计算被排除验证者百分比的上限

    代码如下:

    cursor.execute("SELECT COUNT(*) FROM t_validators WHERE f_slashed") n_to_remove = 2 * cursor.fetchone()[0] print(f"number of slashed/slasher validators: {n_to_remove}") cursor.execute("SELECT COUNT(*) FROM t_validators WHERE f_activation_epoch = 0") n_genesis = cursor.fetchone()[0] print(f"minimum number of active validators: {n_genesis}") print(f"upper-bound percentage of validators excluded from average: {100 * n_to_remove / n_genesis:.1f}%")

    output:
    
      number of slashed/slasher validators: 268
    
      minimum number of active validators: 21063
    
      upper-bound percentage of validators excluded from average: 1.3%
    
    
    输出:
    
       slashed/slasher 验证者的数量:268
    
       最小活跃验证者数量:21063
    
       从平均值中排除的验证者的上限比例:1.3%
    

    正如上述计算所示,在最坏的情况下,我们从研究中排除了 1.3% 的验证者。因此,我们推测,这并不会对平均净奖励值的计算产生显著影响。 [注意:用于计算平均奖励并将相关数据存储在Chaind数据库中的代码,是在本文章写就开始之前运行的。]

    计算和绘制模型和平均奖励

    代码如下:

    def base_reward(active_balance, effective_balance=int(32e9)): return effective_balance * 64 // math.isqrt(active_balance) // 4 cursor.execute(f"SELECT * FROM t_epoch_extras WHERE f_epoch < {END_EPOCH} ORDER BY f_epoch") _, aggregate_reward, aggregate_reward_nonslashed, active_balance_nonslashed = list(zip(*cursor.fetchall())) average_reward_nonslashed = [] modelled_reward = [] for e in range(END_EPOCH): # look at the average reward received by a supposed 32 ETH validator # (almost all validators have a 32 ETH effective balance) average_reward_nonslashed.append(32e9 * aggregate_reward_nonslashed[e] / active_balance_nonslashed[e]) p = average_p_rate b = base_reward(active_balance[e]) modelled_reward.append(3*b*p**2-3*b*(1-p)+(7/8)*b*p**2*math.log(p)/(p-1)+(1/8)*b*p**2) rewards = pd.DataFrame({ 'average_reward_nonslashed': average_reward_nonslashed, 'modelled_reward': modelled_reward }) fig = plt.figure(figsize=(12, 8)) ax1=fig.add_subplot(111, label='1') ax2=fig.add_subplot(111, label='2', frame_on=False) ax1.plot(range(END_EPOCH), average_reward_nonslashed, label='actual reward') ax1.plot(range(END_EPOCH), modelled_reward, label='modelled reward') ax1.legend() ax2.plot([datetime(2020,12,1,12,0,23), datetime(2021,4,22,17,13,59)], [0,0], linestyle='None') ax2.set_title('Modeled vs. average reward for an Ethereum validator') ax1.set_xlabel('epoch') ax1.set_ylabel('Average per-epoch reward (gwei)') ax2.xaxis.tick_top() ax2.xaxis.set_major_formatter(mdates.DateFormatter('%b-%Y')) ax2.xaxis.set_label_position('top') ax2.yaxis.set_visible(False);

    img

    (图名:以太坊验证者模型预测奖励与实际奖励之对比)

    如上图所示,我们构建简单模型似乎与实际情况非常相近,但总体而言,它预测的奖励值略高于实际奖励值。让我们计算一下溢价程度:

    计算模型溢价

    代码如下:

    premium = [modelled_reward[i] / a - 1 for i, a in enumerate(average_reward_nonslashed)] average_premium = sum(premium) / len(premium) print(f"on average, the model predicted {100 * average_premium:.2}% " f"higher rewards than were observed in practice")

    output: on average, the model predicted 1.7% higher rewards than were observed in practice 输出: 平均而言,该模型预测的奖励比实际数值高 1.7%

    因此,在平均1.7%的溢价程度下,我们的模型捕捉到了每 epoch 平均奖励的绝大多数变动。 在所有的汇总数据中,略微溢价的程度似乎也相当一致。要了解这种差异的来源,我们需要进一步研究单个验证者的表现。


    验证者表现

    正如上一篇文章所述,任何既定验证者可获得的实际奖励在某种程度上取决于运气。特别是因为,分配给每个验证者的任务数量可能会有很大差异。另一种情况是,当验证者的证明由于skip slot(“跳块”,即该 slot 没有区块)而被延迟时,验证者也可能会错失奖励,而这不是验证者的过错。

    如果我们想比较验证者的表现,我们需要分析每个验证者如何利用其验证机会。一种方法是,查看完美履行验证职责的验证者 (在考虑了网络其余部分的性能缺陷情况下),可以获得什么级别的奖励。因为这会是验证者在整个时期内能累积获得的最大奖励。然后,我们将这一数值与一名验证者在此期间获得的实际净奖励值 (即所有获得的奖励的总和减去因不完全证明而产生的任何处罚) 进行对比。

    我们可以将实际奖励与最大奖励的比率定义为****验证者效率****。换句话说,即既定验证者可获得奖励的比例。然后,我们可以将验证者效率作为衡量验证者表现的指标,并生成一个排名,将效率最佳的验证者排在首位。验证者奖励由两部分构成:区块提议奖励,偶尔且随机发生;以及区块证明奖励,每个时段 (6.4 分钟) 执行一次。 让我们先看看区块证明奖励的表现。

    区块证明效率

    在模型中,我们假设验证者要么完美地履行他们的证明职责,要么根本不履行职责。然而,如上面的数据所示,正是这一假设导致我们对实际奖励的估计略高。因为存在验证者履行了证明职责但并没有获得全部奖励的情况。要了解这是如何发生的,我们需要更仔细地研究验证者实际获得的奖励。前一篇文章中已提到,对于以下四类证明行为,验证者每完成一项,最多可以获得一个 base_reward 基础奖励:

    1.对“源”区块进行正确投票。对最终达成 CasperFFG共识的“源”检查点区块进行投票证明。在实践中,只有源检查点投票正确的情况下,证明才能被打包,因此“源”投票奖励可以等效地视为“打包奖励”。

    (注:当一个block的高度恰好是"epoch"值的整数倍时,这个block便不会包含任何投票信息,而是包含了当前所有的签名者列表。这个block被叫做checkpoint。可以看出,checkpoint类似于一个“里程碑”,可以用来表示“到目前为止,有效的签名者都记录在我这里了”。)

    1. 对“目标”区块进行正确投票。对最终达成 CasperFFG共识的目标检查点区块进行投票证明。

    2. 对区块链头进行正确投票。对区块链头部区块进行投票。

    4.打包延迟。最终的基础奖励在将区块证明者和区块提议者之间进行分配。证明者最多收到 7/8 的基础奖励,比例与打包延迟的 slot 数成反比。区块提议者将收到由其打包的每条证明基础奖励的 1/8。

    请注意,有关“源”、“目标”和“区块头”投票的含义和功能的完整详细信息,请参阅 Gasper 论文

    验证者获得奖励 (1) 的比例完全由其网络参与程度决定。如果验证者没有对正确的“源”区块进行投票,他们的证明根本不会被打包在内,也就意味着验证者根本没有参与进来。 奖励 (2) 和 (3) 略有不同——验证者投票给他们认为是“正确”的目标区块或头部区块,但一些验证者对区块链的认知可能不完整或延迟,这导致他们投票结果与该时隙最终确定的区块结果不同。在这种情况下,投票给“正确”区块的验证者会收到部分 基础奖励,分配比例基于以相同方式投票的验证者的有效质押比例。 投票给“不正确”区块的验证者的基础奖励将被罚没。

    关于情况(4),也就是打包延迟奖励,之前已经讨论过:我们的模型假设,“在线”验证者只有在 slot 内无区块可打包的情况下才会延迟证明。但在实操中,证明行为也可能因其他因素而延迟。比如说,由于网络延迟,区块提议者没有及时收到证明投票信息,因而无法将其打包入最早的区块中。

    为了理解这些数据背后的含义,我们将对单个验证者的表现展开详细研究。首先是创世验证者1111。照例,我们将使用chaind数据库工具,但对于一些额外的数据,我们将单独使用Python 脚本支持模块进行处理。.

    获取验证者 1111 的证明效率

    代码如下:

    cursor.execute( f"SELECT * FROM t_validator_epoch_extras WHERE f_validator_index = 1111 AND f_epoch = {END_EPOCH}" ) _, _, _, reward, max_reward, missed, target, head, delay, _, _ = cursor.fetchone() print(f"max reward: {max_reward/1e9:.4f} ETH") print(f"actual reward: {reward/1e9:.4f} ETH") print(f"attestation efficiency = {100*reward/max_reward:.1f}%") shortfall = max_reward - reward print(f"shortfall {shortfall/1e9:.4f} ETH") print(f"...due to missed attestations: {missed/1e9:.4f} ETH ({100*missed/shortfall:.1f}%)") print(f"...due to incorrect head: {head/1e9:.4f} ETH ({100*head/shortfall:.1f}%)") print(f"...due to incorrect target: {target/1e9:.4f} ETH ({100*target/shortfall:.1f}%)") print(f"...due to excess delay: {delay/1e9:.4f} ETH ({100*delay/shortfall:.1f}%)\n\n") fig = plt.figure() ax = fig.add_axes([0,0,1,1]) ax.axis('equal') ax.pie( [missed, head, target, delay], labels=['missed attestation', 'incorrect head', 'incorrect target', 'excess delay'], autopct='%1.1f%%' ) ax.set_title( f'Causes of attestation shortfall for validator 1111 during the first {END_EPOCH} beacon chain epochs' ) plt.show()

    output:
    
      max reward: 1.3370 ETH
    
      actual reward: 1.3116 ETH
    
      attestation efficiency = 98.1%
    
      shortfall 0.0254 ETH
    
      ...due to missed attestations: 0.0147 ETH (57.8%)
    
      ...due to incorrect head:    0.0082 ETH (32.3%)
    
      ...due to incorrect target:   0.0023 ETH (9.1%)
    
      ...due to excess delay:     0.0002 ETH (0.8%)
    
    输出:
    
       最大奖励:1.3370 ETH
    
       实际奖励:1.3116 ETH
    
       证明效率 = 98.1%
    
       损失 0.0254 ETH
    
       ...由于错过证明:0.0147 ETH (57.8%)
    
       ...由于头部错误:0.0082 ETH (32.3%)
    
       ...由于目标错误:0.0023 ETH (9.1%)
    
       ...由于过度延迟:0.0002 ETH (0.8%)
    
    

    img

    (图名:在信标链前32000 时段中造成验证者1111证明奖励损失的因素)

    因此,对于前 32000 个时段,验证者 1111 的证明效率为 98%,这意味着其获得的实际奖励值为最大奖励值的98%。考虑到因机会成本、遗漏或错误证明而受到的惩罚,我们可以将证明损失的起因归结如下。57.8%是由于遗漏而没有参与证明,32.3%是由于错误的头部区块投票,9.1% 是由于错误的目标区块投票,0.8% 是由于该证明投票的“过度延迟”。在这里,过度延迟是指延迟超过了证明本应被打包的最早区块了,其中除去跳块 (skipped slots) 的情况。

    错误的头部区块投票、错误的目标区块投票和过度延迟这些因素并没有包含在我们之前的模型中,也就导致了模型预测的验证者奖励与实际情况的轻微出入。

    区块提议效率

    验证者获得奖励的另一活动是区块提议。但是,在这种情况下,评估验证者表现有点棘手。因为区块提议的奖励取决于该区块中打包的证明投票数量。如果验证者未能在需要时及时提议新区块,我们也不能说如果成功提议了该区块会有多少奖励。即使验证者确实成功地提议了一个新区块,也可能出现该区块实际上并没有打包足够多的证明投票的情况(这可能是由于网络延迟或其他因素,这些因素可能是,也可能不是区块提议者的错)。

    因此,为了对验证者效率进行整体测量,我们将不得不对区块提议奖励做出一些假设。对于成功提议的区块,我们将计算收到的实际提议奖励,并假设这是最大可能的奖励。 对于跳过或者丢失的区块,我们将使用以下公式预估理论上的区块提议奖励 Rb,具体如下:

    img

    其中,BE 指每个ETH的基础奖励,SA 指该epoch内的证明押金的 ETH数。 1/256这个数字是基于提议者将获得其所打包的见证投票的 1/8 的基础奖励,并且提议者奖励在每个epoch 中的所有 32 个提议者平分(即1/(8*32))。

    同样,让我们看一下验证者 1111的情况,看看这在实操中意味着什么。

    获取验证者 1111 的提议效率

    代码如下:

    cursor.execute( f"SELECT SUM(f_proposer_duties), SUM(f_proposals_included) FROM t_validator_epoch_summaries " f"WHERE f_validator_index = 1111 AND f_epoch <= {END_EPOCH}" ) proposer_duties, proposals_included = cursor.fetchone() cursor.execute( f"SELECT f_block_reward, f_missed_block_reward FROM t_validator_epoch_extras " f"WHERE f_validator_index = 1111 AND f_epoch = {END_EPOCH}" ) proposer_reward, missed_proposer_reward = cursor.fetchone() print( f"validator 1111 was allocated {proposer_duties} proposer duties and fulfilled {proposals_included} " f"({100*proposals_included/proposer_duties:.0f}%)" ) max_proposer_reward = proposer_reward + missed_proposer_reward print(f"max proposer reward = {max_proposer_reward/1e9:.4f} ETH") print( f"received proposer reward = {proposer_reward/1e9:.4f} ETH ({100*proposer_reward/max_proposer_reward:.0f}%)" )

    output:
    
      validator 1111 was allocated 19 proposer duties and fulfilled 17 (89%)
    
    max proposer reward = 0.0531 ETH
    
    received proposer reward = 0.0464 ETH (88%)
    
    输出:
    
    验证者 1111 被分配了 19 个提议任务并完成了 17 个 (89%)
    
    最大提议奖励 = 0.0531 ETH
    
    收到提议奖励 = 0.0464 ETH (88%)
    

    可见,在这种情况下,尽管验证者成功履行了 89% 的区块提议职责,但效率仍略低,仅为 88%。 这是因为错过的区块提议的价值略高于平均水平。这些区块提议出现在分析时期(epoch 17334 和 17520 )的后期,验证者集的规模以及区块提议的价值都在整个时期普遍增加。

    验证者效率

    观察成功提议区块或错失该机会而产生的ETH 价值增减有一个好处,就是我们现在对区块提议职责的提议“奖励损失”有了测量的标准。而将此与区块证明效率相结合,我们可以计算整体的验证者效率,即验证者所能获得的最大奖励的百分比。基于我们对那些区块的预期奖励,我们可以重新绘制上面的饼图,显示由于错过提议而造成的奖励损失比例。

    获取验证者 1111 的验证者效率

    代码如下:

    cursor.execute( f"SELECT * FROM t_validator_epoch_extras WHERE f_validator_index = 1111 AND f_epoch = {END_EPOCH}" ) _, _, _, att_reward, max_att_reward, missed, target, head, delay, prop_reward, missed_props = cursor.fetchone() max_reward = max_att_reward + prop_reward + missed_props actual_reward = att_reward + prop_reward print(f"max reward: {max_reward/1e9:.4f} ETH") print(f"actual reward: {actual_reward/1e9:.4f} ETH") print(f"validator efficiency = {100*actual_reward/max_reward:.1f}%") shortfall = max_reward - actual_reward print(f"shortfall {shortfall/1e9:.4f} ETH") print(f"...due to missed attestations: {missed/1e9:.4f} ETH ({100*missed/shortfall:.1f}%)") print(f"...due to incorrect head: {head/1e9:.4f} ETH ({100*head/shortfall:.1f}%)") print(f"...due to incorrect target: {target/1e9:.4f} ETH ({100*target/shortfall:.1f}%)") print(f"...due to excess delay: {delay/1e9:.4f} ETH ({100*delay/shortfall:.1f}%)") print(f"...due to missed proposals: {missed_props/1e9:.4f} ETH ({100*missed_props/shortfall:.1f}%)") fig = plt.figure() ax = fig.add_axes([0,0,1,1]) ax.axis('equal') ax.pie( [missed, head, target, delay, missed_props], labels=['missed attestation', 'incorrect head', 'incorrect target', 'excess delay', 'missed proposals'], autopct='%1.1f%%' ) ax.set_title( f'Causes of reward shortfall for validator 1111 during the first {END_EPOCH} beacon chain epochs' ) plt.show()

    output:
    
      max reward: 1.3901 ETH
    
      actual reward: 1.3580 ETH
    
      validator efficiency = 97.7%
    
      shortfall 0.0321 ETH
    
      ...due to missed attestations: 0.0147 ETH (45.9%)
    
      ...due to incorrect head:    0.0082 ETH (25.7%)
    
      ...due to incorrect target:   0.0023 ETH (7.2%)
    
      ...due to excess delay:     0.0002 ETH (0.7%)
    
      ...due to missed proposals:   0.0066 ETH (20.6%)
    
    输出:
    
       最大奖励:1.3901 ETH
    
       实际奖励:1.3580 ETH
    
       验证者效率 = 97.7%
    
       损失 0.0321 ETH
    
       ...由于错过见证:0.0147 ETH (45.9%)
    
       ...由于头部错误:0.0082 ETH (25.7%)
    
       ...由于目标错误:0.0023 ETH (7.2%)
    
       ...由于过度延迟:0.0002 ETH (0.7%)
    
       ...由于错过提案:0.0066 ETH (20.6%)
    

    在信标链前32000 时段中造成1111验证者奖励损失的因素

    img

    (饼图名称:在信标链前32000 时段中造成1111验证者奖励损失的因素)

    验证者比较

    我们已经发现我们的示例验证者1111的验证者效率大约为 98%。 这意味着该验证者几乎从协议中获得了最大的可得奖励。但这有多典型呢?为了进行比较,我们选择了一个大验证者集,看其验证者效率分布。

    为了在尽可能长的时期内做出比较,我们选择了那些仍在 epoch 32000 履行验证职责的创世验证者(也就是说他们没有被罚没或自愿退出)。 为简单起见,我们还将排除少数被扣除了奖励的验证者,以及获得额外 eth1 保证金质押的验证者。然后,让我们来计算集合中每个验证者的验证效率并查看分布情况吧。

    找出排除了扣除过奖励、罚没者和重复存款者的验证者集

    代码如下:

    FUTURE_EPOCH = 2**64 - 1 # from spec cursor.execute( "SELECT f_index, f_activation_epoch, f_exit_epoch, f_slashed, f_public_key " "FROM t_validators ORDER BY f_index" ) validators = [{ 'index': r[0], 'activation_epoch': FUTURE_EPOCH if r[1] is None else r[1], 'exit_epoch': FUTURE_EPOCH if r[2] is None else r[2], 'slashed': r[3], 'slasher': False, 'redeposit': False, 'pubkey': r[4].hex() } for r in cursor.fetchall()] pubkey_lookup = {v['pubkey']: v for v in validators} # identify slashers cursor.execute("SELECT f_inclusion_slot FROM t_proposer_slashings") proposer_slashings = [s[0] for s in cursor.fetchall()] cursor.execute("SELECT f_inclusion_slot FROM t_attester_slashings") slashings = proposer_slashings + [s[0] for s in cursor.fetchall()] for s in slashings: cursor.execute(f"SELECT f_validator_index FROM t_proposer_duties WHERE f_slot = {s}") validators[cursor.fetchone()[0]]["slasher"] = True # check for any instances of validator deposits made to already active validators cursor.execute("SELECT f_inclusion_slot, f_validator_pubkey FROM t_deposits") for row in cursor.fetchall(): deposit_slot = row[0] pubkey = row[1].hex() if pubkey not in pubkey_lookup: continue else: validator = pubkey_lookup[pubkey] if deposit_slot // 32 > validator["activation_epoch"] and deposit_slot // 32 < validator["exit_epoch"]: validator["redeposit"] = True reduced_genesis_set = [ v for v in validators if v["activation_epoch"] == 0 and not ( v["slashed"] or v["slasher"] or v["redeposit"] or v["exit_epoch"] < END_EPOCH ) ] n_genesis = sum(1 for v in validators if v["activation_epoch"] == 0) n_reduced = sum(1 for v in reduced_genesis_set if v["activation_epoch"] == 0) print(f"validator count (genesis set): {n_genesis}") print(f"validator count (reduced genesis set): {n_reduced} ({n_genesis - n_reduced} excluded)")

    output:
    
     validator count (genesis set): 21063
    
     validator count (reduced genesis set): 20993 (70 excluded)
    
    输出:
    
      验证者数量(创世验证者集):21063
    
      验证者数量(筛选后的创世验证者集):20993(减少 70)
    

    计算筛选后的创世验证者集

    代码如下:

    for v in reduced_genesis_set: cursor.execute( f"SELECT * FROM t_validator_epoch_extras " f"WHERE f_validator_index = {v['index']} AND f_epoch = {END_EPOCH}" ) ( _, _, _, v['att_reward'], v['max_att_reward'], v['missed_att_shortfall'], v['incorrect_target_shortfall'], v['incorrect_head_shortfall'], v['excess_delay_shortfall'], v['proposer_reward'], v['missed_proposer_reward'] ) = cursor.fetchone() max_reward = v['max_att_reward'] + v['proposer_reward'] + v['missed_proposer_reward'] v['validator_efficiency'] = (v['att_reward'] + v['proposer_reward']) / max_reward x = [100 * v['validator_efficiency'] for v in reduced_genesis_set if v['validator_efficiency']] fig, ax = plt.subplots(figsize=(12, 8)) ax.hist(x, bins=[e / 10 for e in range(800,1000)]) ax.set_xlim(xmin=80, xmax=100) ax.set_title(f'Validator efficiency for reduced genesis set up to epoch {END_EPOCH}') ax.set_xlabel('Validator efficiency (%)') ax.set_ylabel('Validator count') ax.set_xticks(range(80,101,2)) quartiles = statistics.quantiles(x) percentiles = statistics.quantiles(x, n=100) print("validator efficiency statistics:\n") print(f" minimum = {min(x):.2f}%") print(f" 1st percentile = {percentiles[0]:.2f}%") print(f" lower quartile = {quartiles[0]:.2f}%") print(f" median = {quartiles[1]:.2f}%") print(f" upper quartile = {quartiles[2]:.2f}%") print(f"99th percentile = {percentiles[98]:.2f}%") print(f" maximum = {max(x):.2f}%")

    output:
    
      validator efficiency statistics:
    
      
    
    ​      minimum = -77.02%
    
       1st percentile = 73.49%
    
       lower quartile = 97.30%
    
    ​       median = 98.61%
    
       upper quartile = 98.99%
    
      99th percentile = 99.25%
    
    ​      maximum = 99.43%
    
    输出:
    
       验证者效率数据:
    
      
    
    ​       最小值 = -77.02%
    
       第 1 个百分位数 = 73.49%
    
       最小四分位数 = 97.30%
    
    ​       中位数 = 98.61%
    
       最大四分位数 = 98.99%
    
       第 99 个百分位数 = 99.25%
    
    ​       最大值 = 99.43%
    

    img

    (图名:截止到epoch 32000筛选后的创世验证者集的验证者效率分布)

    因此,事实上验证者效率的中位数超过 98%,而我们的示例验证者 1111 的效率为 97.7%,这意味着按照这个指标,验证者1111的效率低于一半的验证者。

    如图所示,大多数验证者的验证者效率分布在 97% - 99% 之间,没有验证者达到完美 (100%) 的效率。 另一方面,少数验证者的效率实际上是负的(即他们在此期间的净奖励为负)。 这表明存在少数从未验证成功的验证者,这可能是由于验证者密钥丢失或该验证者不理解成功验证所需的技术步骤。

    矩形图似乎显示出双峰分布,大多数验证者不是分布在 97% - 99% 范围内,而是分布在 92% - 97% 范围内。 让我们来看看这两组验证者,看看这一结果差异是否有明确的原因。

    显示两个验证者小组的损失图表

    代码如下:

    group1_shortfalls = [0.0] * 5 group2_shortfalls = group1_shortfalls.copy() group1_count = group2_count = 0 for v in reduced_genesis_set: if v['validator_efficiency'] >= 0.92 and v['validator_efficiency'] < 0.97: group1_shortfalls[0] += v['missed_att_shortfall'] group1_shortfalls[1] += v['incorrect_head_shortfall'] group1_shortfalls[2] += v['incorrect_target_shortfall'] group1_shortfalls[3] += v['excess_delay_shortfall'] group1_shortfalls[4] += v['missed_proposer_reward'] group1_count += 1 elif v['validator_efficiency'] >= 0.97: group2_shortfalls[0] += v['missed_att_shortfall'] group2_shortfalls[1] += v['incorrect_head_shortfall'] group2_shortfalls[2] += v['incorrect_target_shortfall'] group2_shortfalls[3] += v['excess_delay_shortfall'] group2_shortfalls[4] += v['missed_proposer_reward'] group2_count += 1 labels = ['missed\nattestation', 'incorrect\nhead', 'incorrect\ntarget', 'excess\ndelay', 'missed\nproposals'] width = 0.35 x1 = [x - width/2 for x in range(len(labels))] x2 = [x + width/2 for x in range(len(labels))] y1 = [y / group1_count / 1e9 for y in group1_shortfalls] y2 = [y / group2_count / 1e9 for y in group2_shortfalls] fig, ax = plt.subplots(figsize=(12, 8)) rects1 = ax.bar(x1, y1, width, label='group 1 (92% ≤ efficiency < 97%)') rects2 = ax.bar(x2, y2, width, label='group 2 (97% ≤ efficiency)') ax.set_xticks(range(len(labels))) ax.set_xticklabels(labels) ax.set_ylabel('Per-validator shortfall (ETH)') ax.set_title('Comparing causes of validator shortfall between validator groups') ax.legend();

    img

    (图名:两组验证者其验证奖励损失原因之对比)

    比较两组验证者,我们可以发现,表现较差的组(组 1)在 5种情况下都表现更差。 然而,让两组产生显著损失的主要原因是组1更大程度的错过了证明投票机会。这说明原因只是验证者们的正常运行时间的不同。至于为什么这些验证者正常运行时间不同,并且几乎明显均分为两组?原因尚不清楚。(也许是因为某些验证者是“业余爱好者”,而另一些是“专业机构”?) 要分析错过证明投票机会所导致的验证奖励的损失程度,我们需要找到一个与组1平均情况相近的验证者。

    找出一个与组1平均情况相近的验证者

    代码如下:

    target = int(group1_shortfalls[0] / group1_count) cursor.execute( f"SELECT f_validator_index FROM t_validator_epoch_extras " f"WHERE f_epoch = {END_EPOCH} AND f_validator_index < 21063 " f"ORDER BY ABS(f_shortfall_missed - {target}) LIMIT 1" ) val_index = cursor.fetchone()[0] cursor.execute( f"SELECT COUNT(*) FROM t_validator_epoch_summaries " f"WHERE f_validator_index = {val_index} AND f_epoch <= {END_EPOCH} AND NOT f_attestation_included" ) n_missed = cursor.fetchone()[0] frac = n_missed / END_EPOCH eff = 100 * validators[val_index]['validator_efficiency'] print(f"validator {val_index} had a validator efficiency of {eff:.1f}% and missed {n_missed} attestations") print(f"this was {100 * frac:.1f}% of the total ({24 * 365 / 12 * frac:.1f} hours of downtime per month)")

    output:
    
      validator 18071 had a validator efficiency of 94.4% and missed 660 attestations
    
    this was 2.1% of the total (15.1 hours of downtime per month)
    
    输出:
    
      验证者18071  验证者效率为 94.4%,错过了 660 次证明
    
      占总证明次数的 2.1%(每月离线 15.1 小时)
    

    真是非常令人鼓舞!尽管每月平均离线 15.1 小时,但表现不佳的组1中的验证者平均捕获可得奖励仍然超过 94%。

    把 epoch 32000 的验证者表现数据写入 csv 文件

    代码如下:

    with open('validator_performance.csv', 'w') as f: writer = csv.writer(f) writer.writerow( [ 'validator', 'efficiency', 'attestation_reward', 'max_attestation_reward', 'missed_attestation_shortfall', 'target_shortfall', 'head_shortfall', 'excess_delay_shortfall', 'proposer_reward', 'missed_proposer_reward','max_reward' ] ) for v in validators: cursor.execute( f"SELECT * FROM t_validator_epoch_extras " f"WHERE f_validator_index = {v['index']} AND f_epoch = {END_EPOCH}" ) result = cursor.fetchone() if result is None: continue ( _, _, _, att_reward, max_att_reward, missed_att_shortfall, target_shortfall, head_shortfall, excess_delay_shortfall, proposer_reward, missed_proposer_reward ) = result max_reward = max_att_reward + proposer_reward + missed_proposer_reward efficiency = (att_reward + proposer_reward) / max_reward writer.writerow( [ v['index'], efficiency, att_reward, max_att_reward, missed_att_shortfall, target_shortfall, head_shortfall, excess_delay_shortfall, proposer_reward, missed_proposer_reward, max_reward ] )

    有关在整个验证者集中单个验证者表现的详细信息,可以通过 CSV 文件查询。 该文件有每个验证者在前 32,000 个 epoch中的验证者效率,以及具体奖励损失情况(以 gwei 为单位)。


    总结

    在这篇文章中,我们看到,我们以前的验证人奖励模型与实际获得的奖励相当接近。然而,当我们深入研究细节时,就会发现,验证者获得的实际收益与他们本应获得的收益之间的很大一部分差异,是由于我们的模型中未包含某些错综复杂的验证者奖励机制。

    因此,我们使用了测量验证者表现的指标——验证者效率——以便公平地对各验证者展开比较。 这一指标应用于前 32,000 个信标链 epoch中,结果是大多数验证者都获得了绝大多数可得奖励——即使是表现最差的四分之一也实现了超过 97% 的验证者效率。这意味着对于大多数验证者来说,提高奖励的机会将非常有限。但是,如果验证者收入远低于最大奖励值,最可能的解释应该是验证者的离线时间。因此,对于大多数收入较低的验证者而言,应该首先关注如何提高在线时间。


    致谢

    我在本项目中使用了 chaind工具,非常感谢 Jim McDonald 的耐心支持和帮助。 还要感谢 Lakshman Sankar 和 Barnabé Monnot 的建议与反馈。 照片由 Unsplash 上的 Henrik Hansen 提供。



    ECN的翻译工作旨在为中国以太坊社区传递优质资讯和学习资源,文章版权归原作者所有,转载须注明原文出处以及ethereum.cn,若需长期转载,请联系eth@ecn.co进行授权。

    Ethereum Community Network
    以太坊社区网络
    Ethereum Community Network
    以太坊社区网络