來源 | pintail.xyz

作者 | pintail

建模分析 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()

建模分析 Altair 升級的影響

提議者和滯後獎勵

在上面的圖表中,要注意的第一項變化是提議者獎勵提升至原來的 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

建模分析 Altair 升級的影響

因此,在 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');
    輸出:Altair 升級前的年度獎勵數據 (ETH)    -----------------------------------------                  中位數 : 2.1031                    平均數 : 2.1038      標準差 : 0.0181     四分位差 : 0.0200       Altair 升級後的年度獎勵數據 (ETH)    -------------------------------------                  中位數 : 2.0951                    平均數 : 2.1038      標準差 : 0.1025     四分位差 : 0.1400      相對面積 : 5.7 倍 (standard deviation 標準差) / 7.0 倍 (interquartile range 四分位差)

建模分析 Altair 升級的影響

如預期般,上面的統計數據和圖表顯示,在驗證者完美參與的情況下,儘管 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 計價) first 62,000 epochs (pre-Altair rewards scheme, measured in ETH)    ------------------------------------------------------------------------中位數 : 2.1649                   平均數 : 2.1387     標準差 : 0.1459     四分位差 : 0.0551      統計數據— 前 62,000 個 epochs(Altair 升級後的獎勵方案,以 ETH 計價) first 62,000 epochs (Altair rewards scheme, measured in ETH)    ------------------------------------------------------------------------中位數 : 2.1241 平均數 : 2.1144 標準差 : 0.1775 四分位差 : 0.1344 比較 -----------在 Altair 方案下,平均每個驗證者的獎勵下降了 1.1%  在 Altair 方案下,獎勵的四分位差 (面積分布) 提高了 2.4 倍

建模分析 Altair 升級的影響

比較驗證者分佈

比較統計數據,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%, 最大增幅 : 1.38%

建模分析 Altair 升級的影響

如上圖所示,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()

建模分析 Altair 升級的影響

總結

從我們最初的建模可以看出,把提議者的獎勵提升到 4 倍,以及引入同步委員將造成獎勵的差異比目前的情況更大。這種差異將對個人驗證者帶來比大型質押池 (它們的獎勵將接近於平均水平) 更大的影響。在考慮任何未來可能影響驗證者獎勵結構的變更時 (例如以後引入分片),都應牢記這個影響。

此外,根據上文的分析,似乎 Altair 升級激活後,平均獎勵可能會有所減少,儘管引入的變化在最大可得獎勵方面是中立的。受這個影響最大的是那些經常滯後提交證明的驗證者,可能是網路延遲造成的。因此,驗證者最好在 Altair 升級激活前密切關注自己的表現。

特別是,參與度降低或出塊失敗會導致證明打包延遲,在 Altair 方案下驗證者獎勵很可能會下降得更明顯。到目前爲止,這種情況僅在信標鏈上發生過一次,但我們可以預期隨着未來的更新 (尤其是合併和分片) 引入新的複雜性,這種事件在未來是可能發生的。

致謝

非常感謝 Lido finance 通過它們的生態系統資助計劃對這項工作提供資金。一如既往地,感謝 Jim McDonald 提供 chaind 的數據,讓這個分析得以完成,還要感謝 Barnabé Monnot 和 Vasiliy Shapovalov 的寶貴反饋意見。圖片由 Mathias Krumbholz 提供,添加了箭頭。

點擊“閱讀原文”獲取文章內部鏈接!

原文鏈接:https://pintail.xyz/posts/modelling-the-impact-of-altair/

ECN 的翻譯工作旨在爲中國以太坊社區傳遞優質資訊和學習資源,文章版權歸原作者所有,轉載須註明原文出處以及 ETH 中文站。若需長期轉載,請聯繫 eth@ecn.co 進行授權。

建模分析 Altair 升級的影響