摘要:只需要簡單地將重置難度炸彈時回退的區塊數設置成和分叉的 FORK_BLOCK_NUMBER(分叉區塊號)相同即可(或者稍微調整一下)。而到君士坦丁堡硬分叉時,僞塊號被重置爲了 2,280,000,也就是說時期變爲了 22,這意味着每個區塊的難度額外增加了 2 22。

關於以太坊難度計算的所有信息

每當以太坊的定時炸彈爆炸時,總會有兩個問題出現在我們面前。第一個問題(也可以說是更重要的一個問題)是:“什麼時候出塊會變慢,那簡直不能忍。” 第二個問題是,“這一次,我們應該將炸彈推遲多久?”

在這篇短文中,我爲第二個問題提出了一個簡單得微不足道的解答。難度炸彈應該被推遲多久呢?我的建議是:“ 給定需要在區塊高度 N 處執行硬分叉,則將難度炸彈推遲 N 個區塊(爲了增加安全性,也可以更少)。”

我並不打算在這篇文章中解釋難度應該如何計算( 如果你想了解,可以參閱我的上一篇文章 )。下面這張圖片在我的上一篇文章中已經出現過了,其中列出了難度的計算公式。

當前難度值 d c 是由上一個難度值 d p 與兩部分相加而來。第一部分基於當前區塊與上一個區塊之間的時間間隔來調整難度等級。令 s = ts c -t s ,當 s < 10 時,上面公式中的因子 max(x) 的計算結果爲 1(假設採用整除法)。在這種情況下,出塊太快會導致難度增加,從而降低出塊速度。如果 10<= s < 20,因子 max(x) 將變成 0,從而確保當 s 接近目標時間 14 秒時,難度不再調整。而如果 s>=20,因子 max(x) 的值將變成逐步減至 -99,這會導致難度下降,從而提高出塊速度。等式中的第二部分基於上一個區塊的區塊號。通過在指數中對 100,000 進行整除,創建出了一個以 100,000 個區塊爲週期的階躍函數。第二部分也就是我們常說的難度炸彈。如果沒有第二部分的話,難度炸彈便無從談起。

仔細觀察這個公式,注意它由兩部分組成。

公式的第一部分,我稱之爲 “調整”(或 A 部分)。這一部分通過調整當前區塊的難度來矯正上一個區塊出塊時間的偏差。這一調整要麼降低難度,要麼增加難度,這取決於生成上一個區塊所花的時間。上圖中第一個括號突出顯示了這一部分。請花點時間搞清楚它的原理。A 部分的實際效果與設計目標完全一致,它的作用是平抑哈希率的波動對網絡運行的影響(譯者注:哈希率是算力的單位,因此也被用於指代算力)。從下文的圖表中你可以清晰地看出計算公式中的 A 部分的作用完全符合預期。

正因爲 A 部分按照設計正常運作排除了哈希率帶來的影響,所以我認爲,在思考如何推遲定時炸彈時沒必要考慮算力的實際規模。換言之,算力太低不會使難度炸彈爆炸時出塊時間更長、算力太高也不能緩解難度炸彈的效果。鑑於以上分析,我的結論是,公式中的 A 部分與出塊時間的減慢無關。而公式中的第二部分(即導致炸彈的 B 部分)則是導致所有麻煩的罪魁禍首。

我接下來的分析會讓你相信,完全可以將這兩部分分開處理。這些分析使我們更容易看出 A 部分不會對出塊產生影響,而 B 部分 —— 炸彈 —— 纔會導致出塊時間延長。除此以外,你還會知道拆除炸彈是多麼地輕而易舉,只需要在每一次分叉時簡單地將時期重置爲 0 即可。

生成數據並格式化

處理數據的第一步是獲取數據。我們使用自己的軟件庫 TrueBlocks 編寫了如下代碼:

#include “etherlib.h” 
int main(int argc, char *argv[]) {
        init_etherlib();    
        for (int i = 0 ; i < getLatestBlock() ; i++) {    
                CBlock block;     
                getBlock(block, i);       
                cout << block.blockNumber << “,”;        
                cout << block.timestamp << “,”;        
                cout << block.difficulty << end;     
    }     
}

運行上面這段代碼,可以生成以下這種非常簡單的 .csv 格式的數據文件......

區塊號,時間戳,難度值 
0, 1438269960, 17179869184 
1, 1438269988, 17171480576 
2, 1438270017, 17163096064 
… 
8981997, 1574448913, 2432407853358678 
8981998, 1574448935, 2432545292312150 
8981999, 1574448985, 2427931666241578 
8982000, 1574449029, 2424512564668329`

上面的數據再加上從 EtherScan 獲取的每日哈希率列表,對於我們理解以太坊的難度計算來說已經綽綽有餘了。我們使用了 RStudio 和一種名爲 “R” 的數據編程語言來構建下文中的圖表。如果你對 “R” 語言不太熟悉的話,強烈建議你去了解一下,這是一種非常出色的編程語言。

我們先來看一看以太坊的哈希率。

日均哈希率

第一張圖表展示了以太坊主網的 日均哈希率 ,其中的數據來源於 EtherScan。因爲我不知道數據是怎麼收集、創建出來的,因此無法保證這些數據的真實性,但我假定它是對的。可以點擊 這個鏈接查看相關數據

討論: 從上圖中可以明顯地看出,以太坊的哈希率隨着以太幣價格起伏而變化。這張圖讓我回想起了以太坊的價格走勢。2017 年夏,哈希率一路飆升,並在 2018 年第一季度達到頂峯(和幣價一模一樣)。而 2016 年 10 月哈希率的起伏則是由於當時臭名昭著的 DDos 攻擊,上圖中兩條灰色垂線代表的是兩次硬分叉 —— 即拜占庭硬分叉(2017 年 10 月)和君士坦丁堡硬分叉(2019 年 1 月)。這張圖暫時就分析到這裏,不過我們在接下來討論區塊鏈的難度數據時依然會提起它。

原始難度數據

第一張基於難度數據的圖表展現了通過 Parity 的 RPC 調用 get_Block 返回的數據。首先,下面列出了一些標準的統計信息:

以太坊難度值的彙總統計 
0,016,970,000,000,000 —— 最小值 
0,111,700,000,000,000 —— 1/4 
1,926,000,000,000,000 —— 中位數 
1,649,000,000,000,000 —— 平均數 
2,687,000,000,000,000 —— 3/4 
3,702,000,000,000,000 —— 最大值

我們的第一張圖表非常直觀:

討論: 上圖的數據是在第 8,920,000 個區塊時生成的。儘管處理這麼多數據記錄對於 “R” 語言來說易如反掌,但考慮到數據挖掘的迭代性質,我們從每 100 條數據中抽取了 1 條記錄作爲樣本,最終得到大約 9,000 條記錄,並展現在上圖中。與前文一致,上圖中的灰色垂線也代表硬分叉。

紅線的高度( y(x) = difficulty at block.x )代表給定區塊時的難度值。你可以很容易地看出在每一次分叉時拆除難度炸彈的效果。請回看前文中關於哈希率的圖表,在 2016 年秋天的 DDos 攻擊中,你可以看到難度與哈希率之間的關係。

假如難度炸彈沒有拆除 —— 實際上從難度激活的那個高度開始把紅線繼續向上延伸 —— 對於兩次硬分叉都採取同樣的操作,你會發現這與哈希率變化的圖表驚人的一致。換句話說,哈希率和難度是緊密聯繫在一起的,這非常合理。因爲這正是難度計算公式中的 A 部分設計的目的所在。它的存在就是爲了直接根據不同的哈希率來調整難度值。

礦工的行爲可能並不會因爲難度炸彈的拆除而發生變化。他們的挖礦設備在難度炸彈拆除前後都同樣地持續運行。唯一的改變是出塊的平均時間變短了。

在我們進入下一張圖表的分析之前,還有一些地方需要注意。請仔細看看拜占庭硬分叉前面的部分。你會發現難度水平上出現了 4、5 次單向的垂直躍升。事實上,每一次難度水平的跳躍幅度都是上一次的兩倍。這些跳躍就是我們所說的難度炸彈。讓我們一起把目光聚焦到圖表的其它部分。

討論: 我們以 100,000 個區塊爲一組,用垂線將上圖分隔開來。睜大你的眼睛,仔細看,代表難度跳躍的曲線跟我們作的垂線(分隔線)完全重合。在這些分隔線之間,難度持續上升,但上升速度遠不及分隔線上的躍升幅度。難度的爆炸性增長與 2017 年時哈希率一路飆升的事實相一致。

也許你也注意到了每一次連續的 “爆炸” —— 每次炸彈爆炸時的難度值的增量都是上一次的兩倍。事實證明,這些跳躍的週期性對於理解當下正在發生的事情尤爲重要。

在本文餘下的圖表中,我們的目標是將難度計算公式中的第一部分(即 A 部分或 “調整”)和第二部分(即 B 部分或 “爆炸”)分開。從而幫助我們更好地理解如何應對未來的難度炸彈。

每個區塊的難度變化

在下一張圖表中,我們來看看每個連續的區塊之間的難度變化。其計算公式爲 y(x) = 區塊 x 的難度值 — 區塊 x-1 的難度值( y(x) = diff_block_x — diff_block_x_minus_1 )。

討論: 正如我們在上文提到的,A 部分的計算使得難度在一個水平附近 “徘徊”,以確保出塊時間維持在 14 秒。上圖通過使用紅藍色表示數據來揭示這種 “徘徊” 現象。圖中的 “增長” 部分(紅色區域)代表難度的正向變化(即難度變大,區塊產量變低,出塊時間變慢)。而圖中的 “收縮” 部分(藍色區域)代表難度的負向變化(即難度變低,出塊加快,且產量增加)。難度始終圍繞着 0 值 “調整”。換句話說,這部分計算是爲了維持出塊時間的穩定,A 部分的計算使得出塊時間維持在一個穩定的值 —— 14秒。

對你來說,這張圖可能是你第一次看到難度炸彈的直觀展現。很明顯,每次爆炸時的難度都是上一次的兩倍。

但在我看來,這張圖表達的依舊不夠清晰。舉個例子,爲何同樣的圖案在君士坦丁堡期間沒有清晰地重現?其實是因爲哈希率大大提高了。這部分計算可以使出塊時間維持在 14 秒,但是系統的來回波動更加劇烈。這也解釋了爲什麼我們無法在臨近君士坦丁堡分叉時識別出難度炸彈,因爲它被更加劇烈的波動掩蓋了。

那麼,是否存在一些其它的措施,可以讓我們看的更清楚呢?當然!這種辦法確實存在,我們將在下一張圖表中爲你揭曉。

難度的相對變化

本文這部分的最後一張圖表,展示了相對於區塊總難度而言的難度變化百分比。計算公式爲 y(x) = (區塊 x 的難度值 — 區塊 x-1 的難度值)/ 區塊 x-1 的難度值 。而上一張圖表展示的是難度的原始變化。這張圖表展示了標準化的難度變化,從而消除了算力增長帶來的影響,我們從而可以更加清晰地看到難度計算公式中的兩個不同部分 —— A 部分與 B 部分。

下面是每個區塊的難度相對變化圖表:

討論: 現在,你應該明白爲什麼我在前面中說在討論難度炸彈時擔心算力會適得其反得了吧。從這張圖表中,你可以很清晰地看見,區塊生產不受算力增加(或降低)的影響,直到定時炸彈 “抬頭”。公式中的 A 部分使得出塊的速度及產量維持在一個穩定的狀態。在定時炸彈爆炸之前,難度(從平均化和標準化之後的數據看)幾乎不受什麼大的影響。

這裏還有兩件有趣的事情值得你注意:(1)從上圖中,你還可以看見炸彈在圖表的最右側開始 “抬頭”,儘管抬頭的距離比起拜占庭和君士坦丁堡之間的距離要短得多——下文中我會解釋這一現象;(2)圖表底部的條紋是因爲計算公式裏 A 部分中的對 10 整除的那部分而形成;(3)更高的哈希率看上去好像會延遲 “抬頭”,正如 Lane Rettig 在君士坦丁堡分叉之前提到的那樣,我們在上面提到的文章中也寫過。

關於這張圖,我還有許多能繼續分享的,也許哪天我會回過頭來繼續寫。但接下來,我更想爲在未來拆除炸彈提供更好的方法。

拆除炸彈的更佳方式

首先,我要再次強調,在擔心定時炸彈的同時還擔心算力波動會適得其反。計算公式中的 A 部分已經將算力增加(或降低)的影響排除在外了。這也正是 A 部分存在的意義。平坦得近乎完美的難度增量(相對當時的區塊高度而言)證明了 A 部分的工作幾近完美。很顯然,哈希率對出塊時間沒有影響 —— 但這一點我們已經知道了 —— 這也是難度調整設計的目的所在。

炸彈由上述公式末尾的一個額外附加值所定義:

2 ^ (floor(當前區塊號 / 100,000) - 2)
(譯者注:floor(x) 爲 向下取整函數 ,即取不大於 x 的最大整數,例如 floor(3.14) = 3)

上面的式子就是一個以 2 爲底的冪函數。令 p = floor(當前區塊號 / 100,000) ,我們可以將上面的式子改寫爲 2 p 。(你可能會疑惑,式子末尾的 -2 被喫掉了嗎?不是,我們只是暫時忽略它,因爲它的作用不過是讓計算值變小。)最終,我們得到了一個以時段 p 爲步長的階躍函數。

不過,請記住,核心開發人員已經將炸彈重置了兩次 —— 沒說錯,就是 “重置” 的字面意思,他們重設了炸彈計算的週期起點。 這是通過使用 Go 語言代碼創建一個在計算中使用的僞塊來實現的。目前來看,僞塊在過去似乎起到了重置炸彈的效果。下圖這張校正後的圖表顯示了這段時期的真實情況。

上圖用紅色來表示真實的區塊號,其取值範圍是 0 到 8,920,000。而僞塊號(以綠色表示)起初與紅色的真實區塊號重合,直到發生了拜占庭分叉,僞塊號被重置到過去的 3,000,000 個區塊。然後,僞塊號與真塊號平行增長,直到君士坦丁堡分叉,僞塊號再次被重置(這一次被重置了 5,000,000 個區塊)。

下面這張表格中列出了重置後的一些信息,你發現什麼奇怪的地方了麼?

重置 區塊號 回退 僞塊號 時期
拜占庭 4,370,000 3,000,000 1,370,000 13
君士坦丁堡 7,280,000 5,000,000 2,280,000 22
伊斯坦布爾 9,069,000 9,000,000 69,000 0

拜占庭分叉後的僞塊號爲 1,370,000,將該值整除 100,000 可得時期爲 13。也就是說,在哈希率調整之後,每個區塊的難度都額外增加了 2 13 。而到君士坦丁堡硬分叉時,僞塊號被重置爲了 2,280,000,也就是說時期變爲了 22,這意味着每個區塊的難度額外增加了 2 22 。我想,這就是爲什麼定時炸彈爆炸得比我們預期中更早的原因。因爲我們上次重置它時沒有回退得足夠遠。

這次重置定時炸彈的建議值爲回退 69,000 個區塊,從而使得時期變爲 0。這纔是需要重置的正確數量。

如何更好地重置定時的難度炸彈

每當我們必須重置定時炸彈時,有一種非常簡單易行的方法將其重置爲正確的值。通過這種方法,絕大部分和難度炸彈相關的問題將不復存在。只需要簡單地將重置難度炸彈時回退的區塊數設置成和分叉的 FORK_BLOCK_NUMBER(分叉區塊號)相同即可(或者稍微調整一下)。通過這種方式,僞塊號將被設置成接近 0,時期也會變成 0。

由於區塊生產速度的減緩主要是因爲定時炸彈的存在,因此通過上述方式,“抬頭” 效應將變得完全可預測。B 部分的計算完全依賴於僞塊號。如果我們在君士坦丁堡分叉時這麼做(如下表所示),那麼定時炸彈就不會爆炸得那麼快了。

重置 區塊號 回退 僞塊號 時期
拜占庭 4,370,000 4,370,000 0 0
君士坦丁堡 7,280,000 7,280,000 0 0
伊斯坦布爾 9,069,000 9,069,000 0 0

結論

非常感謝你能讀到這裏。我希望這篇文章可以引起那些對難度炸彈感興趣的人的注意。我看到許多人都對難度計算問題感到困惑(不僅僅是普通公衆,還包括核心的開發者以及 Ethereum Magicians)。在我看來,人們想的太複雜了。

以下是幾個關於本文的簡單總結: (1)B 部分的指數性質使得只有它與區塊生產速度有關;(2)計算公式中的 A 部分不會對出塊造成有害影響,相反,它實際上起到了積極的作用;(3)將時期或僞塊重置爲 0 有兩個好處:(a)允許最大限度地推遲定時炸彈,(b)使得炸彈的重現變得非常可預測。

如果你對這篇文章有任何看法,請告訴我。希望我已經幫助大家理解了這些我爛熟於心的內容。

Thomas Jay Rush是軟件公司 TrueBlocks,LLC 的所有者。TrueBlocks 同時也是該公司主要項目的名字。這是一組軟件庫和應用程序的集合。通過 TrueBlocks,你可以對以太坊區塊鏈按照區塊進行實時的智能合約監控與分析。你可以通過 這個網站 與 Rush 取得聯繫。同時,歡迎捐款給: 0xf503017D7baF7FBC0fff7492b751025c6A78179b

(完)

原文鏈接: https://medium.com/@tjayrush/its-not-that-difficult-33a428c3c2c3

作者:Thomas Jay Rush

翻譯&校對:曾汨 & 阿劍

相關文章