本文由哥白尼團隊何思羽創作

挖礦難度

比特幣主鏈上,平均每十分鐘會出一個塊。隨着數字貨幣的發展,參與的 miner 數量與日俱增,挖礦技術日新月異,全網的算力也是以驚人的速度增長。BTC 中爲了保證主鏈平均的高度增加速度依然維持最初設定,進而設置了挖礦難度調整的功能。深入理解挖礦難度的概念,以及挖礦難度調整的方案,對開發人員以及 miner 都很重要,因爲挖礦難度設置不合理可能會導致全網出塊速度極不穩定。本文將詳細介紹 BTC&BCH 挖礦難度及調整方案,我們先從 PoW 算法講起。

1、PoW 算法

PoW (Proof-of-Work)工作量證明算法是一種對應服務與資源濫用、或是阻斷服務攻擊的經濟對策。一般是要求用戶進行一些耗時適當的複雜運算,並且答案能被服務方快速驗算,以此耗用的時間、設備與能源做爲擔保成本,以確保服務與資源是被真正的需求所使用。

PoW 算法具有:去中心化,單向,隨機性,目標難度易調整等特點,所以現在包括 BTC,BCH 在內的很多幣種都採用了 PoW 共識機制。

從實現上來說,PoW 算法的輸入爲任意長度,輸出爲固定長度,比如通常使用 SHA256 算法對應輸出 256-bit。在挖礦過程中 miner 用 PoW 算法計算整個塊頭的 hash 值,由於 SHA256 的特性:塊頭任意一位發生變化,得到的 hash 值會變得完全不一樣,而且大小變化方向不確定。於是,我們比較 hash 值是否小於某個值(實際上這個值是保存在塊頭中的 nBit「解壓後」的 current_target 值)來判斷是否滿足要求;如果小於,則廣播這個區塊;如果不小於,則按照當前挖礦節點的規則改變塊頭中可以改變的值,然後再次計算塊頭 hash 值,以此往復,直到結果小於目標值。

哥白尼開發者:挖礦難度精講

由此可知,current_target 值越小,滿足挖礦要求的概率就越小,挖礦難度就越大。

2、塊頭 & Coinbase 交易

塊頭的生成: 當 miner 開始新一輪打包之後,首先會創建一個空的塊,塊結構分爲塊頭以及塊信息兩部分。先打包塊信息,再根據塊信息填充塊頭。

首先看一下已經成功打包的塊。下圖是寫本文稿時截取最新的 BTC block 詳情。

哥白尼開發者:挖礦難度精講

塊信息 存放的是從 mempool 裏取出來的一系列交易信息,miner 並以此創建了一個 Merkle Tree,交易信息的 hash 值作爲 leaf,最終生成的 Merkle Root 將填到塊頭裏。值得注意的是,交易列表中的第一個是一個非常獨特的交易:Coinbase Transaction。Coinbase Transaction 與普通交易主要的區別有:

1) Coinbase Transaction 不消耗 UTXO

2) input 只有一個,叫做 Coinbase

3) output 的 addresss 爲 miner 的 btc/bch 地址

4) value 由挖礦獎勵和交易費組成

5)更值得注意的是 input 中沒有 Unlocking-script,取而代之的是 Coinbase Data (這部分數據包含 Extra Nonce,在挖礦難度非常高時,將起非常重要的作用)

Coinbase 交易 input 的結構如下:

哥白尼開發者:挖礦難度精講

  • 2013-12-28 BTC 的一個塊的 Coinbase 解析

哥白尼開發者:挖礦難度精講

Coinbase data,該字段數據長度範圍爲 2-byte~100-byte:

  • block height 起初 Coinbase 是不包含塊高度信息,由於重複交易的問題出現,誕⽣了 BIP30,隨後第二套解決方案 BIP34)。BIP34 規定 Coinbase data 最高字節表示用於表示塊高度的數據段的字節數,接下來的字節以⼩端法表示具體的塊高度,創世塊的高度爲 0。

例如:2013-12-28 BTC 的一個塊的 Coinbase 解析中 coinbase data 爲 0x03443b04…,則塊高度用 16 進製表示爲 0x043b44,十進制爲 277316;

  • extra nonce 作爲中間字段,將會在後續提及的 Extra Nonce Solution 詳細說明作用;
  • 上圖 Coinbase data 中用以結尾的「/P2SH/」是 12 年 miner 進行投票支持 BTC 是採用 BIP16 還是 BIP17 的產物,現已棄用。(衆所周知,BIP16 P2SH 獲得了更多票數,被 BTC 採用)

在交易信息聚合完畢得到了 MerkleRoot 之後,接下來填充區塊頭。塊頭結構如下(其中 nBit 就是 PoW 小節提到的 current-target 的壓縮版):

哥白尼開發者:挖礦難度精講

區塊頭 80-byte,一共 6 個字段:

  1. 版本號,允許改變但不推薦
  2. 前一個塊的 hash 值,不允許改變
  3. MerkleRoot 的 hash 值,用於存塊信息裏的交易的 Merkle Tree 的 root 節點的值,允許改變(改變 coinbase 中 input 中的值)
  4. 時間戳,允許基於 MTP11 進行調整改變
  5. nonce,用於 PoW 算法的隨機值,允許改變
  6. nBit,PoW 算法結果必須小於這個數對應的 current_target 才能算塊打包成功。這個值是在每一個塊開始打包之前就確定了,不允許改變

塊頭中 80 bytes 任意一個值發生改變,PoW 的 Hash 結果就會發生改變。

3、挖礦難度及難度調整

理解了 PoW 運算以及運算結果可能受哪些個因素影響,接下來了解一下我們要滿足的難度要求是什麼。

挖礦難度的描述可以認爲有三種形式,difficulty (難度值,浮點數),current_target (當前目標值,256-bit),nbits (32-bit);形式不同其實實質是表達的同一個難度要求,而且這個難度要求在每個塊打包前就確定了。

difficulty 不寫在區塊中,而是以浮點數的形式展現,給人直觀的感受難度程度。 difficulty = difficulty_1 / current_target;

difficulty_1 爲常數:0x00000000ffff0000000000000000000000000000000000000000000000000000

創世塊的 current_target = difficulty_1,所以創世塊的 difficulty = 1.0。

nbits 就是區塊頭 nBits 字段的值,用長度爲 32-bit 的數值表示 256-bit 的數值,是需要犧牲一定精度的 , 可以理解它爲「壓縮」後的 current_target。

在計算 current_target 時,我們先轉換爲二進制然後用公式(a)來計算 256-bit 的 current_target。(值得注意的是:current_target 是一個無符號 256-bit 的值,之所以設置一個 Sign 字段,是爲了與 bitcoind 代碼保持一致,保留符號位參考的是 IEEE 浮點型表示法,其實是無用的)

哥白尼開發者:挖礦難度精講

This compact form is only used in bitcoin to encode unsigned 256-bit numbers which represent difficulty targets, thus there really is not a need for a sign bit, but it is implemented here to stay consistent with bitcoind.

計算 current_target 的公式爲:

哥白尼開發者:挖礦難度精講

例如 nBits = 0x180192d4,

current_target = 0x192d4 * 2 ^ {(8 * (0x18 – 3))}

哥白尼開發者:挖礦難度精講

(最高 16 位爲零)

相較於創世塊,current_target 減小了大約 1/(236) 倍,difficulty 增加了大約 7184404942701 倍。

哥白尼開發者:挖礦難度精講

爲了維持平均每十分鐘生成一個塊的頻率,BTC 中 current_target 設計爲了一個動態值,current_target 根據全網算力的改變而做一些相應的調整,這就是挖礦難度調整。

在 BTC 中,挖礦難度調整 idea 爲:以 2016 個塊(兩週)爲一個週期,每個週期根據前一個週期的實際耗時與理論耗時之間的差別進行調整。

新目標值 = 當前目標值 * 實際 2016 個區塊出塊時間 / 理論 2016 個區塊出塊時間 (2 周)。

方案具體邏輯是:

  1. 判斷是否需要更新目標值 ( 2016 的整數倍),如果不是則繼續使用最後一個區塊的目標值
  2. 計算 2016 個塊實際使用時長:如果用時低於半周,則按半周計算,防止難度增加 4 倍以上;如果用時高於 8 周,則按 8 周計算。防止難度降低到 4 倍以下。
  3. 實際使用時長乘以當前難度,再除以 2 周
  4. 如果超過最大難度限制,則按最大難度處理

計算過程,Go 代碼如下 :

哥白尼開發者:挖礦難度精講

4、BCH 難度調整

BCH 誕生於區塊高度 478558,兩條鏈都採用相同 PoW 共識算法(平均 10 分鐘生成一個塊),所以 miner 可以任意在 BTC 與 BCH 間切換,但由於通常 BCH 全網算力只佔有 BTC 的 7% 左右,當 BCH 獲利大於 BTC 的時候,大量原 BTC miner (尤其是大的礦場)會切入 BCH,一段時間後隨着算力提升,難度值也會提升,miner 會紛紛離開切回 BTC,算力降低,難度居高不下將導致接下來出塊十分困難。倘若繼續沿用 BTC 的難度調整方案,BCH 將無法保證出塊速率穩定在平均 10mins/block,事實上 BCH 的難度值調整算法已經先後經歷了兩種,第一種是緊急難度調整規則(EDA),目前使用的是難度調整規則(DAA)。

緊急難度調整規則(EDA)

EDA 是在沿用 BTC 難度調整算法的基礎上,增加了一個 Emergency Difficulty Adjustment 處理方案,主要是針對於出塊緩慢情況及時降低難度。算法具體邏輯是:對於高度爲 2016 倍數的就拿到此高度前 2016 塊的 blocktime,沿用 BTC 難度調整方案;對於高度非 2016 倍數的塊,則計算生成前六塊的塊總共耗時是否超過 12h,如果超過則降低挖礦難度 20%。 具體實現代碼如下:

哥白尼開發者:挖礦難度精講
哥白尼開發者:挖礦難度精講

難度調整規則(DAA)

然而在運行三個多月中,EDA 表現不盡如人意(非常差),由於其應對出塊速率過高並沒有做相應及時調整而依賴於 BTC 原有的 2016 塊一次的調整機制,所以在面對算力波動時表現並不是很好。

哥白尼開發者:挖礦難度精講

本人分別截取了三段「典型」數據,爲了對上述觀點進行說明:

  1. 2017.8.26 號的算力攻擊,2017.8.27 號大算力切走之後出塊困難,23 小時只出 13 個塊,導致連續調整(降低)挖礦難度。

哥白尼開發者:挖礦難度精講

  1. 2017.10.2 由於礦工趨利性導致 BCH 網絡算力突然增加,僅僅 30mins 就出了 20 多區塊。

哥白尼開發者:挖礦難度精講

  1. 用一個更直觀的數據,BTC 和 BCH 的起跑線都是一樣的,都是 2017 年 8 月 1 日,高度同爲 478,558,而截止到 17 年 11 月 12 日晚 BTC 挖到了 494,079 高度,而 BCH 挖到了 503,815 高度,多了將近 10000 個塊。

哥白尼開發者:挖礦難度精講

所以優化 BCH 的難度調整方案刻不容緩,在得到幾大礦池的穩定算力支持後,17 年 11 月 13 日,BCH 再次升級,就是爲了優化 EDA。BCH 開發團隊(並非社區)收到幾份 DAA,最終採用了 BTCABC 開發團隊 Amaury Sechet 的 DAA 提案。 這份 proposal 的 ieda 可以用一句俗語來形容「魔高一尺,道高一尺,魔有天花板」,根據前一天的算力爲基準從而預測需要設置多少工作量才能耗掉十分鐘。 其實現邏輯如下:

  1. 新算法將在高度 504031 開始生效
  2. 假設需要得到 target_height 的目標難度
  3. (prevBlock – 1)至 (prevBlock – 1 – 2 )這三塊的 ntime,排序,取 ntime 在中間的那塊爲 lastNode
  4. 取(prevBlock – 1 – 144)至 (prevBlock – 1 – 144 – 2)這三塊的 ntime,排序,取 ntime 在中間的那塊爲 firstNode 備註:bch 的目標是 10 分鐘產生一塊,一天產生 144 塊
  5. 根據最近的 144 個區塊的鏈上累計工作量(ChainWork)可以推算出滿足當前算力的所需工作量 work :

哥白尼開發者:挖礦難度精講

  1. 再通過 work 值得出目標難度。

具體實現代碼如下:

哥白尼開發者:挖礦難度精講

計算新 Target 方法如下:

哥白尼開發者:挖礦難度精講

總結下來,DAA 算法具有以下特性:

  • 基於前 144 個塊的算力來逐塊設置挖礦難度;
  • 算力按指數規律變化時,網絡將快速調整難度,保證公平性;
  • 避免當前算力與目標難度的不匹配導致的反饋振盪。
  • 可以一定程度上減少 timestamp manipulation 等攻擊的影響。

哥白尼開發者:挖礦難度精講

DAA 應對算力攻擊的效果如何呢?

以下是兩個算力變化極端場景:算力陡增兩倍,算力陡然減半。 poc 代碼如下:

哥白尼開發者:挖礦難度精講
哥白尼開發者:挖礦難度精講
哥白尼開發者:挖礦難度精講

5、extra nonce 解決方案

目前,BTC 挖礦難度設置到了 7184404942701.792( 0x17272d92 對應的 current_target 爲 0x000000000000000000272d920000000000000000000000000000000000000000)。 也就是說隨機選取的數滿足小於 target 的概率是 1/(272),但是塊頭的 nonce 字段只有 4bytes,也就是 32 位,有可能 232 個隨機數都試完來仍然找不到滿足 target 的 result。所以允許塊頭內部分其他的字段改變,用來生成新的 result。允許改變的字段在第二小節塊頭部分已指明。

試想一下,如果頻繁更改 Coinbase Data 裏的 Extra Nonce,來改變塊頭的 Merkle Root 會怎麼樣?很明顯效率會很低,所以實際挖礦中策略是:儘可能減少塊頭中 Version,TimeStamp,Merkle Root (綠色區域數據)值的改變,而「瘋狂」遍歷 Nonce (紅色區域)的值用於 PoW;當遍歷完沒找到滿足 target 的 result,再改變綠色區域的值,然後繼續「瘋狂」遍歷 Nonce。如此往復直至找到滿足 target 的 result 或者這一輪 PoW 競賽中失敗開始新一輪打包。

哥白尼開發者:挖礦難度精講