Categories
程式開發

深入剖析實現比特幣減半的十五行代碼


北京時間5月12日3時23分左右,比特幣在區塊高度630000處完成誕生以來第三次減半,比特幣區塊獎勵由12.5枚BTC減至6.25枚BTC,剩餘待開採比特幣數量僅剩約262萬。

那麼,什麼是比特幣減半,該事件背後的代碼工作原理是什麼呢?

讓我們一起深入了解其中有趣的細節吧。

區塊補貼和獎勵減半

先來簡單回顧一下一個基礎知識點,所謂*“礦工”,是指此時此刻分佈於世界各地,正在運行硬件和軟件計算下一個比特幣區塊哈希值*的人。

如果礦工們及時解決了比特幣區塊鍊網絡中的數學難題,他們就可以獲得區塊獎勵

以上描述裡出現了不少時髦的流行語,是不是?這就來讓我們對它們進行逐個解釋。

什麼是哈希值?

比特幣整個採礦的概念設計得十分巧妙。實際上,採礦並不是一個冥思苦想解題的過程,它更多的是一種嘗試猜出一個神奇數字的蠻力嘗試。

比特幣網絡幾乎在所有地方都採用了SHA256哈希算法。世界各地的礦工都在嘗試運行的採礦功能,可以用下面這個簡化版本的函數來表示:

SHA256(
    $previousBlockHash,
    $newTransactionsToBeIncluded,
    $magicNumber
);

其中$magicNumber也被稱為隨機數(nonce),在密碼學中是指一個只被使用一次的數字。通過在新區塊的哈希計算中包含以前區塊的哈希值,實際上就形成了一個鍊式結構,將每個以前的區塊逐個鏈接,一直鏈接到新的區塊為止。因此,區塊鏈本質上就是個高端版本的鍊錶

礦工們所做的就是一直不斷地猜測$magicNumber的值。他們一遍又一遍地運行相同的計算,用遞增(或隨機)的窮舉法來試$magicNumber的有效值。通過這樣的計算方式,礦工每次都會改變上述函數的哈希值結果。

那他們什麼時候算“贏”呢?

一旦他們找到一個開頭0的數量足夠多的哈希值

就是這樣:開頭有足夠多的0

這個答案就是如此簡單而粗暴,看起來並不高大上。而採礦計算的難度就取決於哈希值的開頭需要多少個0。當第一個區塊被開採出來,它的哈希值開頭只有8個0:

00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048

而在編寫本文時,該區塊鏈計算出來的第629828個區塊的哈希值,它開頭有19個0

0000000000000000000133e7bffe43530e508183ec48a89bad23a370692b16e8

而開頭需要的0數量越多,就越難猜出這個隨機數

這裡多說一句,比特幣這個算法的精巧之處在於,它每隔2016個區塊會自動重新計算其難度目標(即開頭需要多少個0),但本文就不再為此贅述了。

採礦獎勵

如果你猜對了這個隨機數,你就會得到獎勵。這種獎勵以新鑄造出來的比特幣的形式發放。

本質上,這些比特幣是憑空產生的。只不過它既不便宜也不是信手拈來。比特幣網絡中採礦是要花費大量計算能力和電量的。付出這些辛勤勞動後,你得到了比特幣。為了維護鞏固這個比特幣網絡,你作為一名礦工會得到理所應當的獎勵。

這些新鑄造的比特幣是在所謂的*生成交易(Coinbase Transaction)*中創建的。這是一種獨特的交易,它包括在每個區塊之中,其作用是支付一定數量的比特幣獎勵給猜中了正確哈希值的礦工。

是的,熱門的“Coinbase加密貨幣交換所”的名字就來源於這個詞彙,它原本是指每個區塊中對礦工提供獎勵的特殊交易(生成交易)的引用。

每一個被正確計算出來的區塊,會自動計算出礦工獎勵,並隨著比特幣網絡的發展而對獎勵金額進行自動調整。它的工作原理也設計得十分巧妙,請讓我來帶你了解一下背後的代碼。

GetBlockSubsidy()函數

礦工獎勵減半的魔法發生在函數GetBlockSubsidy()中,該函數包含在源代碼的src/validt.cpp文件中。

CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams)
{
    int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
    // Force block reward to zero when right shift is undefined.

if (halvings >= 64)
plain
        return 0;

CAmount nSubsidy = 50 * COIN;

// Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.
nSubsidy >>= halvings;
return nSubsidy;
}

那麼,這個函數包含什麼功能?讓我們來剖析一下代碼。雖然我已經有一段時間沒碰C語言了,但幸運的是,這段代碼相當容易讀懂。

已經發生過多少次比特幣減半了?

讓我們從頂部開始看:

int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;

上面最後一個共識參數consensusParams.nSubsidyHalvingInterval,在src/chainparms.cpp文件中被定義為共識規則的一部分。這些是比特幣網絡上每個人都必須遵守的一套公共規則。

這裡需要說明的是,對這些共識規則的任何更改都將創建一個“硬分叉(Hard fork)”,即一組不再遵循原始區塊鏈規則的新規則。

consensus.nSubsidyHalvingInterval = 210000;

這個常量表示,每產生210,000個區塊,就會出現一次比特幣減半。
其中變量nHeight指的是當前的區塊高度(Height)。或者換句話說,已經開采出來的區塊的數量。

而5月12日比特幣開採數量就達到630,000個區塊了。所以此時,這個等式就變為:

int halvings = 630000 / 210000;

這將使減半次數值變為3。這也是比特幣網絡第三次將其區塊獎勵減半。

但是如果結果是一個浮點值,顯然這個結果並不符合該變量所聲明的int類型,那會發生什麼事情呢?比如:

int halvings = 620000 / 210000;

這將得到2.952380952的計算結果。但是,由於變量被定義為整數類型,因此會通過直接抹去小數點後面的值,而取整為該結果範圍內最小的整數值。所以,這時的減半次數是2。

區塊獎勵結束

讓我們來看看這個代碼片段:

// Force block reward to zero when right shift is undefined.

if (halvings >= 64)

return 0;

因為nSubsidy是個64位的有符號整數,在執行右移操作時可能出現未定義行為,這意味著x >> 65操作會變成x >> 1,就會導致數值環回,所以這裡需要保留對減半次數大於等於64的檢查。這對於上面所貼的後續代碼很重要。

最初的比特幣核心代碼並沒有包含這個bug的修復,直到2014年才合併了這個pull request

這部分經常被誤解為“將會有64次比特幣減半”。這並不正確。其實只會有33次比特幣減半,後面我們會看到關於這一點的解釋。

你能得到多少比特幣作為獎勵?

“減半”一詞其實指的是對礦工可以獲得的比特幣數量進行限制。每采出210,000個區塊,這個獎勵金額就會減為一半

CAmount nSubsidy = 50 * COIN;

nSubsidy >>= halvings;
return nSubsidy;

一開始,在10多年前,在網絡上每開采出一個區塊,就會獎勵礦工50個比特幣。

然後,在210,000個區塊被開采之後,這個獎勵金額被減半為25個比特幣。又采出210,000個區塊之後,變成了12.5個比特幣。這就是截止到這次減半之前的情況。

而這次減半之後,比特幣採礦獎勵又會被減為一半,也即是說礦工只能得到6.25個比特幣的獎勵。之後再采出210,000個區塊,獎勵就會變成3.125。以此類推。

這段代碼中有一個巧妙的位運算操作,我想在這裡強調一下:

nSubsidy >>= halvings;

我打賭你並不是每天都能在自己的代碼中看到這樣的>>=運算符。

讓我們為每個變量填上實際的值,重寫代碼如下:

CAmount nSubsidy = 50 * 100000000;

nSubsidy >>= 3;
return nSubsidy;

區塊獎勵有一個固定的初始值50。而按照src/amount.h文件中的定義,每枚比特幣又可分為1億個更小的基本單位(Satoshi,即“聰”)。如果想要說得更準確的話,礦工不會得到1整個“比特幣”作為獎勵,而是將1個比特幣均分為1億份,然後得到“1億”份這樣的基本單位作為獎勵。

而這樣1億份基本單位加在一起等於1個比特幣。

這給出了nSubsidy的初始值為50億基本單位。如果用二進制來表示,可以得到如下數字:

100101010000001011111001000000000

nSubsidy >>= 3表示將位右移3位。這就得到了:

100101010000001011111001000000

當你將該二進制值轉換為其十進製表示形式時,會得到625,000,000。換句話說,這就是625,000,000個基本單位或6.25個比特幣。

在下一次減半發生時,會隨著右移一位讓上面的二進制數值末尾的0又減少一個,從而有效地再次將獎勵金額減半。

因為初始值50億總共只有33比特位,我們將在第33比特減半後讓區塊獎勵變為0,這大約會發生在2140年。

且這個假設的前提是,到那個時候,交易所支付的採礦獎勵應該還足以補償礦工的電力消耗和硬件成本。

結論

比特幣的貨幣供應量通過硬編碼進行了限制,每個人都可以看到和審查這段代碼。

礦工數量龐大,即使不是百萬數量級,至少也是數以千計,能讓這麼多礦工能以和諧的方式一起工作的想法真的令人感到不可思議。如果你曾經嘗試設置過任意一個包含5個節點的集群,你就會明白要在多個節點間達成共識或多數仲裁是多麼困難的一件事情,因為你需要像比特幣網絡一樣考慮到:

  • 如何讓所有節點達成共識?
  • 如何防止結果發生偏差?
  • 如何防止網絡連接中斷造成的影響?
  • 如何防止數據損失造成的影響?
  • 如果一個節點在執行寫操作的過程中崩潰了怎麼辦?

而在比特幣網絡上這一切都正常運轉,而且是在硬件、網速、節點版本和軟件均為未知的條件下運行的——這都是因為設計時所包含的數學原理和社會共識,讓每個人在該網絡上都遵循相同的規則。

這真是令人著迷的技術啊。

作者介紹:

Mattias Geniar,獨立開發人員,Linux系統管理員和通用問題解決者。

英文原文:

Dissecting the code responsible for the Bitcoin halving