精通比特幣-第8章 挖礦與共識
8.1 簡介
挖礦是增加比特幣貨幣供應的一個過程。挖礦同時還保護著比特幣系統的安全,防止欺詐交易,避免“雙重支付”,“雙重支付”是指多次花費同一筆比特幣。礦工們通過為比特幣網路提供算力來換取獲得比特幣獎勵的機會。
礦工們驗證每筆新的交易並把它們記錄在總帳簿上。每10分鐘就會有一個新的區塊被“挖掘”出來,每個區塊裡包含著從上一個區塊產生到目前這段時間內發生的所有交易,這些交易被依次新增到區塊鏈中。我們把包含在區塊內且被新增到區塊鏈上的交易稱為“確認”交易,交易經過“確認”之後,新的擁有者才能夠花費他在交易中得到的比特幣。
礦工們在挖礦過程中會得到兩種型別的獎勵:建立新區塊的新幣獎勵,以及區塊中所含交易的交易費。為了得到這些獎勵,礦工們爭相完成一種基於加密雜湊演算法的數學難題,這些難題的答案包括在新區塊中,作為礦工的計算工作量的證明,被稱為”“工作量證明”。該演算法的競爭的機制以及獲勝者有權在區塊鏈上進行交易記錄的機制,這二者是比特幣安全的基石。
新比特幣的生成過程被稱為挖礦是因為它的獎勵機制被設計為速度遞減模式,類似於貴重金屬的挖礦過程。比特幣的貨幣是通過挖礦發行的,類似於中央銀行通過印刷銀行紙幣來發行貨幣。礦工通過創造一個新區塊得到的比特幣數量大約每四年(或準確說是每210,000個塊)減少一半。開始時為2009年1月每個區塊獎勵50個比特幣,然後到2012年11月減半為每個區塊獎勵25個比特幣。之後將在2016年的某個時刻再次減半為每個新區塊獎勵12.5個比特幣。基於這個公式,比特幣挖礦獎勵以指數方式遞減,直到2140年。屆時所有的比特幣(20,999,999.98)全部發行完畢。換句話說在2140年之後,不會再有新的比特幣產生。
礦工們同時也會獲取交易費。每筆交易都可能包含一筆交易費,交易費是每筆交易記錄的輸入和輸出的差額。在挖礦過程中成功“挖出”新區塊的礦工可以得到該區塊中包含的所有交易“小費”。目前,這筆費用佔礦工收入的0.5%或更少,大部分收益仍來自挖礦所得的比特幣獎勵。然而隨著挖礦獎勵的遞減,以及每個區塊中包含的交易數量增加,交易費在礦工收益中所佔的比重將會逐漸增加。在2140年之後,所有的礦工收益都將由交易費構成。
“挖礦”這個詞有一定的誤導性。它容易引起對貴重金屬採礦的的聯想,從而使我們的注意力都集中在每個新區塊產生的獎勵上。儘管挖礦帶來的獎勵是一種激勵,但它最主要的目的並不是獎勵本身或者新幣的產生。如果只把挖礦看作生產新幣的過程,那你是把手段(激勵措施)當成了目的。挖礦是一種將結算所去中心化的過程,每個結算所對處理的交易進行驗證和結算。挖礦保護了比特幣系統的安全,並且實現了在沒有中心機構的情況下,也能使整個比特幣網路達成共識。
挖礦這個發明使比特幣變得很特別,這種去中心化的安全機制是點對點的電子貨幣的基礎。鑄造新幣的獎勵和交易費是一種激勵機制,它可以調節礦工行為和網路安全,同時又完成了比特幣的貨幣發行。
在本章中,我們先來審視比特幣的貨幣發行機制,然後再來了解挖礦的最重要的功能:支撐比特幣安全的去中心化的自發共識機制。
8.1.1 比特幣經濟學和貨幣創造
通過創造出新區塊,比特幣以一個確定的但不斷減慢的速率被鑄造出來。大約每十分鐘產生一個新區塊,每一個新區塊都伴隨著一定數量從無到有的全新比特幣。每開採210,000個塊,大約耗時4年,貨幣發行速率降低50%。在比特幣執行的第一個四年中,每個區塊創造出50個新比特幣。
2012年11月,比特幣的新發行速度降低到每區塊25個比特幣,並且預計會在2016年的某個時刻,在第420,000個區塊被“挖掘”出來之後降低到12.5比特幣/區塊。在第13,230,000個區塊(大概在2137年被挖出)之前,新幣的發行速度會以指數形式進行64次“二等分”。到那時每區塊發行比特幣數量變為比特幣的最小貨幣單位——1聰。最終,在經過1,344萬個區塊之後,所有的共20,999,999.9769聰比特幣將全部發行完畢。換句話說,到2140年左右,會存在接近2,100萬比特幣。在那之後,新的區塊不再包含比特幣獎勵,礦工的收益全部來自交易費。圖8-1展示了在發行速度不斷降低的情況下,比特幣總流通量與時間的關係。
在例8-1的程式碼展示中,我們計算了比特幣的總髮行量。
例8-1 比特幣發行總量的計算指令碼
# 初始的塊獎勵為50BTC
start_block_reward = 50
# 以10分鐘為一個區塊的間隔,210000個塊共約4年時間
reward_interval = 210000
def max_money():
# 50 BTC = 50 0000 0000 Satoshis
current_reward = 50 * 10**8
total = 0
while current_reward > 0:
total += reward_interval * current_reward
current_reward /= 2
return total
print "Total BTC to ever be created:", max_money(), "Satoshis"
例8-2顯示了這個指令碼的執行結果。
例8-2 執行 max_money.py 指令碼
$ python max_money.py
Total BTC to ever be created: 2099999997690000 Satoshis
圖8-1 比特幣貨幣供應速度隨著時間發生幾何級降低
總量有限並且發行速度遞減創造了一種抗通脹的貨幣供應模式。法幣可被中央銀行無限制地印刷出來,而比特幣永遠不會因超額印發而出現通脹。
通貨緊縮貨幣
最重要並且最有爭議的一個結論是一種事先確定的發行速率遞減的貨幣發行模式會導致貨幣通貨緊縮(簡稱通縮)。通縮是一種由於貨幣的供應和需求不匹配導致的貨幣增值的現象。它與通脹相反,價格通縮意味著貨幣隨著時間有越來越強的購買力。
許多經濟學家提出通縮經濟是一種無論如何都要避免的災難型經濟。因為在快速通縮時期,人們預期著商品價格會下跌,人們將會儲存貨幣,避免花掉它。這種現象充斥了日本經濟“失去的十年”,就是因為在需求坍塌之後導致了滯漲狀態。
比特幣專家們認為通縮本身並不壞。更確切地說,我們將通縮與需求坍塌聯絡在一起是因為過去出現的一個特例。在法幣屆,貨幣是有可能被無限制印刷出來的,除非遇到需求完全崩塌並且毫無發行貨幣意願的情形,因此經濟很難進入滯漲期。而比特幣的通縮並不是需求坍塌引起的,它遵循一種預定且有節制的貨幣供應模型。
實際上,通縮貨幣會讓賣家考慮到折現的影響,容易誘發過度的囤積本能,除非這部分折現率超過買家的囤積本能。因為買賣雙方都有囤積的動機,這兩種折現率會因為雙方的囤積本能相互抵消,而達成一個平衡價格。因此即使在比特幣價格貼現率為30%的情況下,大部分使用比特幣的零售商並不會感受到花費比特幣很困難,也能因此盈利。當然,比特幣這種不是因經濟快速衰退而引起的通縮,是否會引發其他問題,仍有待觀察。
8.2 去中心化共識
在上一章中我們瞭解了區塊鏈。可以將區塊鏈看作一本記錄所有交易的公開總帳簿(列表),比特幣網路中的每個參與者都把它看作一本所有權的權威記錄。
但在不考慮相信任何人的情況下,比特幣網路中的所有參與者如何達成對任意一個所有權的共識呢?所有的傳統支付系統都依賴於一箇中心認證機構,依靠中心機構提供的結算服務來驗證並處理所有的交易。比特幣沒有中心機構,幾乎所有的完整節點都有一份公共總帳的備份,這份總帳可以被視為認證過的記錄。區塊鏈並不是由一箇中心機構創造的,它是由比特幣網路中的所有節點各自獨立競爭完成的。換句話說比特幣網路中的所有節點,依靠著節點間的不穩定的網路連線所傳輸的資訊,最終得出同樣的結果並維護了同一個公共總帳。這一章將介紹比特幣網路不依靠中心機構而達成共識的機制。
中本聰的主要發明就是這種去中心化的自發共識機制。這種自發,是指沒有經過明確選舉或者沒有固定達成的共識的時間。換句話說,共識是數以千計的獨立節點遵守了簡單的規則通過非同步互動自發形成的產物。所有的比特幣屬性,包括貨幣、交易、支付以及不依靠中心機構和信任的安全模型等都是這個機制的衍生物。比特幣的去中心化共識由所有網路節點的4種獨立過程相互作用而產生:
▷ 每個全節點依據綜合標準對每個交易進行獨立驗證
▷ 通過完成工作量證明演算法的驗算,挖礦節點將交易記錄獨立打包進新區塊,
▷ 每個節點獨立的對新區塊進行校驗並組裝進區塊鏈
▷ 每個節點對區塊鏈進行獨立選擇,在工作量證明機制下選擇累計工作量最大的區塊鏈
在接下來的幾節中,我們將審視這些過程,瞭解它們之間如何相互作用並達成全網的自發共識,從而使任意節點組合出它自己的權威、可信、公開的總帳。
8.3 交易的獨立校驗
在第5章中,我們知道了錢包軟體通過收集UTXO、提供正確的解鎖指令碼、構造支付給接收者的輸出這一系列的方式來建立交易。產生的交易隨後將被髮送到比特幣網路臨近的節點,從而使得該交易能夠在整個比特幣網路中傳播。
然而,在交易傳遞到臨近的節點前,每一個收到交易的比特幣節點將會首先驗證該交易,這將確保只有有效的交易才會在網路中傳播,而無效的交易將會在第一個節點處被廢棄。
每一個節點在校驗每一筆交易時,都需要對照一個長長的標準列表:
▷交易的語法和資料結構必須正確。
▷輸入與輸出列表都不能為空。
▷交易的位元組大小是小於MAX_BLOCK_SIZE
的。
▷每一個輸出值,以及總量,必須在規定值的範圍內 (小於2,100萬個幣,大於0)。
▷沒有雜湊等於0,N等於-1的輸入(coinbase交易不應當被中繼)。
▷nLockTime是小於或等於INT_MAX
的。
▷交易的位元組大小是大於或等於100的。
▷交易中的簽名數量應小於簽名運算元量上限。
▷解鎖指令碼(scriptSig
)只能夠將數字壓入棧中,並且鎖定指令碼(scriptPubkey
)必須要符合isStandard
的格式 (該格式將會拒絕非標準交易)。
▷池中或位於主分支區塊中的一個匹配交易必須是存在的。
▷對於每一個輸入,如果引用的輸出存在於池中任何的交易,該交易將被拒絕。
▷對於每一個輸入,在主分支和交易池中尋找引用的輸出交易。如果輸出交易缺少任何一個輸入,該交易將成為一個孤立的交易。如果與其匹配的交易還沒有出現在池中,那麼將被加入到孤立交易池中。
▷對於每一個輸入,如果引用的輸出交易是一個coinbase輸出,該輸入必須至少獲得COINBASE_MATURITY
(100)個確認。
▷對於每一個輸入,引用的輸出是必須存在的,並且沒有被花費。
▷使用引用的輸出交易獲得輸入值,並檢查每一個輸入值和總值是否在規定值的範圍內 (小於2100萬個幣,大於0)。
▷如果輸入值的總和小於輸出值的總和,交易將被中止。
▷如果交易費用太低以至於無法進入一個空的區塊,交易將被拒絕。
▷每一個輸入的解鎖指令碼必須依據相應輸出的鎖定指令碼來驗證。
這些條件能夠在比特幣標準客戶端下的AcceptToMemoryPool
、CheckTransaction
和CheckInputs
函式中獲得更詳細的闡述。請注意,這些條件會隨著時間發生變化,為了處理新型拒絕服務攻擊,有時候也為交易型別多樣化而放寬規則。
在收到交易後,,每一個節點都會在全網廣播前對這些交易進行校驗,並以接收時的相應順序,為有效的新交易建立一個池(交易池)。
8.4 挖礦節點
在比特幣網路中,一些節點被稱為專業節點礦工。第1章中,我們介紹了Jing,在中國上海的計算機工程專業學生,他就是一位礦工。Jing通過礦機挖礦獲得比特幣,礦機是專門設計用於挖比特幣的計算機硬體系統。Jing的這臺專業挖礦裝置連線著一個執行完整比特幣節點的伺服器。與Jing不同,一些礦工是在沒有完整節點的條件下進行挖礦,正如我們在“8.11.2 礦池”一節中所述的。與其他任一完整節點相同,Jing的節點在比特幣網路中進行接收和傳播未確認交易記錄。然而,Jing的節點也能夠在新區塊中整合這些交易記錄。
同其他節點一樣,Jing的節點時刻監聽著傳播到比特幣網路的新區塊。而這些新加入的區塊對挖礦節點有著特殊的意義。礦工間的競爭以新區塊的傳播而結束,如同宣佈誰是最後的贏家。對於礦工們來說,獲得一個新區塊意味著某個參與者贏了,而他們則輸了這場競爭。然而,一輪競爭的結束也代表著下一輪競爭的開始。新區塊並不僅僅是象徵著競賽結束的方格旗;它也是下一個區塊競賽的發令槍。
8.5 整合交易至區塊
驗證交易後,比特幣節點會將這些交易新增到自己的記憶體池中。記憶體池也稱作交易池,用來暫存尚未被加入到區塊的交易記錄。與其他節點一樣,Jing的節點會收集、驗證並中繼新的交易。而與其他節點不同的是,Jing的節點會把這些交易整合到一個候選區塊中。
讓我們繼續跟進,看下Alice從Bob咖啡店購買咖啡時產生的那個區塊(參見“2.1.2 買咖啡”)。Alice的交易在區塊277,316。為了演示本章中提到的概念,我們假設這個區塊是由Jing的挖礦系統挖出的,並且繼續跟進Alice的交易,因為這個交易已經成為了新區塊的一部分。
Jing的挖礦節點維護了一個區塊鏈的本地副本,包含了自2009年比特幣系統啟動執行以來的全部區塊。當Alice買咖啡的時候,Jing節點的區塊鏈已經收集到了區塊277,314,並繼續監聽著網路上的交易,在嘗試挖掘新區塊的同時,也監聽著由其他節點發現的區塊。當Jing的節點在挖礦時,它從比特幣網路收到了區塊277,315。這個區塊的到來標誌著終結了產出區塊277,315競賽,與此同時也是產出區塊277,316競賽的開始。
在上一個10分鐘內,當Jing的節點正在尋找區塊277,315的解的同時,它也在收集交易記錄為下一個區塊做準備。目前它已經收到了幾百筆交易記錄,並將它們放進了記憶體池。直到接收並驗證區塊277,315後,Jing的節點會檢查記憶體池中的全部交易,並移除已經在區塊277,315中出現過的交易記錄,確保任何留在記憶體池中的交易都是未確認的,等待被記錄到新區塊中。
Jing的節點立刻構建一個新的空區塊,做為區塊277,316的候選區塊。稱作候選區塊是因為它還沒有包含有效的工作量證明,不是一個有效的區塊,而只有在礦工成功找到一個工作量證明解之後,這個區塊才生效。
8.5.1 交易塊齡,礦工費和優先順序
Jing的比特幣節點需要為記憶體池中的每筆交易分配一個優先順序,並選擇較高優先順序的交易記錄來構建候選區塊。交易的優先順序是由交易輸入所花費的UTXO的“塊齡”決定,交易輸入值高、“塊齡”大的交易比那些新的、輸入值小的交易擁有更高的優先順序。如果區塊中有足夠的空間,高優先順序的交易行為將不需要礦工費。
交易的優先順序是通過輸入值和輸入的“塊齡”乘積之和除以交易的總長度得到的:
Priority = Sum (Value of input * Input Age) / Transaction Size
在這個等式中,交易輸入的值是由比特幣單位“聰”(1億分之1個比特幣)來表示的。UTXO的“塊齡”是自該UTXO被記錄到區塊鏈為止所經歷過的區塊數,即這個UTXO在區塊鏈中的深度。交易記錄的大小由位元組來表示。
一個交易想要成為“較高優先順序”,需滿足的條件:優先值大於57,600,000,相當於一個比特幣(即1億聰),年齡為一天(144個區塊),交易的大小為250個位元組:
High Priority > 100,000,000 satoshis * 144 blocks / 250 bytes = 57,600,000
區塊中用來儲存交易的前50K位元組是保留給較高優先順序交易的。Jing的節點在填充這50K位元組的時候,會優先考慮這些最高優先順序的交易,不管它們是否包含了礦工費。這種機制使得高優先順序交易即便是零礦工費,也可以優先被處理。
然後,Jing的挖礦節點會選出那些包含最小礦工費的交易,並按照“每千位元組礦工費”進行排序,優先選擇礦工費高的交易來填充剩下的區塊,區塊大小上限為MAX_BLOCK_SIZE
。
如區塊中仍有剩餘空間,Jing的挖礦節點可以選擇那些不含礦工費的交易。有些礦工會竭盡全力將那些不含礦工費的交易整合到區塊中,而其他礦工也許會選擇忽略這些交易。
在區塊被填滿後,記憶體池中的剩餘交易會成為下一個區塊的候選交易。因為這些交易還留在記憶體池中,所以隨著新的區塊被加到鏈上,這些交易輸入時所引用UTXO的深度(即交易“塊齡”)也會隨著變大。由於交易的優先值取決於它交易輸入的“塊齡”,所以這個交易的優先值也就隨之增長了。最後,一個零礦工費交易的優先值就有可能會滿足高優先順序的門檻,被免費地打包進區塊。
比特幣交易中沒有過期、超時的概念,一筆交易現在有效,那麼它就永遠有效。然而,如果一筆交易只在全網廣播了一次,那麼它只會儲存在一個挖礦節點的記憶體中。因為記憶體池是以未持久化的方式儲存在挖礦節點儲存器中的,所以一旦這個節點重新啟動,記憶體池中的資料就會被完全擦除。而且,即便一筆有效交易被傳播到了全網,如果它長時間未處理,它將從挖礦節點的記憶體池中消失。如果交易本應該在一段時間內被處理而實際沒有,那麼錢包軟體應該重新發送交易或重新支付更高的礦工費。
現在,Jing的節點從記憶體池中整合到了全部的交易,新的候選區塊包含有418筆交易,總的礦工費為0.09094925個比特幣。你可以通過比特幣核心客戶端命令列來檢視這個區塊,如例8-3所示:
例8-3 區塊277,316
{
"hash" : "0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4",
"confirmations" : 35561,
"size" : 218629,
"height" : 277316,
"version" : 2,
"merkleroot" :
"c91c008c26e50763e9f548bb8b2fc323735f73577effbc55502c51eb4cc7cf2e",
"tx":[
"d5ada064c6417ca25c4308bd158c34b77e1c0eca2a73cda16c737e7424afba2f",
"b268b45c59b39d759614757718b9918caf0ba9d97c56f3b91956ff877c503fbe",
... 417 more transactions ...
],
"time" : 1388185914,
"nonce" : 924591752,
"bits" : "1903a30c",
"difficulty" : 1180923195.25802612,
"chainwork" :
"000000000000000000000000000000000000000000000934695e92aaf53afa1a",
"previousblockhash" :
"0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569",
"nextblockhash" :
"000000000000000010236c269dd6ed714dd5db39d36b33959079d78dfd431ba7"
}
8.5.2 創幣交易
區塊中的第一筆交易是筆特殊交易,稱為創幣交易或者coinbase交易。這個交易是由Jing的節點構造並用來獎勵礦工們所做的貢獻的。Jing的節點會建立“向Jing的地址支付25.09094928個比特幣”這樣一個交易,把生成交易的獎勵傳送到自己的錢包。Jing挖出區塊獲得的獎勵金額是coinbase獎勵(25個全新的比特幣)和區塊中全部交易礦工費的總和。如例8-4所示:
$ bitcoin-cli getrawtransaction
d5ada064c6417ca25c4308bd158c34b77e1c0eca2a73cda16c737e7424afba2f 1
例8-4 創幣交易
{
"hex" :
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f03443b0403858402062f503253482fffffffff0110c08d9500000000232102aa970c592640d19de03ff6f329d6fd2eecb023263b9ba5d1b81c29b523da8b21ac00000000",
"txid" : "d5ada064c6417ca25c4308bd158c34b77e1c0eca2a73cda16c737e7424afba2f",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"coinbase" : "03443b0403858402062f503253482f", "sequence" : 4294967295
}
],
"vout" : [
{
"value" : 25.09094928,
"n":0, "
scriptPubKey" : {
"asm" : "02aa970c592640d19de03ff6f329d6fd2eecb023263b9ba5d1b81c29b523da8b21OP_CHECKSIG",
"hex" : "2102aa970c592640d19de03ff6f329d6fd2eecb023263b9ba5d1b81c29b523da8b21ac",
"reqSigs" : 1,
"type" : "pubkey",
"addresses" : [
"1MxTkeEP2PmHSMze5tUZ1hAV3YTKu2Gh1N"
]
}
}
],
"blockhash" :
"0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4",
"confirmations" : 35566,
"time" : 1388185914,
"blocktime" : 1388185914
}
與常規交易不同,創幣交易沒有輸入,不消耗UTXO。它只包含一個被稱作coinbase的輸入,僅僅用來建立新的比特幣。創幣交易有一個輸出,支付到這個礦工的比特幣地址。創幣交易的輸出將這25.09094928個比特幣傳送到礦工的比特幣地址,如本例所示的1MxTkeEP2PmHSMze5tUZ1hAV3YTKu2Gh1N。
8.5.3 Coinbase獎勵與礦工費
為了構造創幣交易,Jing的節點需要計算礦工費的總額,將這418個已新增到區塊交易的輸入和輸出分別進行加總,然後用輸入總額減去輸出總額得到礦工費總額,公式如下:
Total Fees = Sum(Inputs) - Sum(Outputs)
在區塊277,316中,礦工費的總額是0.09094925個比特幣。
緊接著,Jing的節點計算出這個新區塊正確的獎勵額。獎勵額的計算是基於區塊高度的,以每個區塊50個比特幣為開始,每產生210,000個區塊減半一次。這個區塊高度是277,316,所以正確的獎勵額是25個比特幣。
詳細的計算過程可以參看比特幣核心客戶端中的GetBlockValue函式,如例8-5所示:
例8-5 計算區塊獎勵—Function GetBlockValue, Bitcoin Core Client, main.cpp, line 1305
int64_t GetBlockValue(int nHeight, int64_t nFees)
{
int64_t nSubsidy = 50 * COIN;
int halvings = nHeight / Params().SubsidyHalvingInterval();
// 如果右移的次數未定義,區塊獎勵強制為零
if (halvings >= 64)
return nFees;
// Subsidy每210,000個區塊減半一次,大概每4年發生一次
nSubsidy >>= halvings;
return nSubsidy + nFees;
}
變數nSubsidy表示初始獎勵額,值為COIN
常量(100,000,000聰)與50的乘積,也就是說初始獎勵額為50億聰。
緊接著,這個函式用當前區塊高度除以減半間隔(SubsidyHalvingInterval
函式)得到減半次數(變數halvings
)。每210,000個區塊為一個減半間隔,對應本例中的區塊277316,所以減半次數為1。
變數halvings
最大值64,如果超出這個值,程式碼算得的獎勵額為0,整個函式將只返回礦工費總額,作為獎勵總額。
然後,這個函式會使用二進位制右移操作將獎勵額(變數nSubsidy
)右移一位(等同與除以2),每一輪減半右移一次。在這個例子中,對於區塊277,316只需要將值為50億聰的獎勵額右移一次,得到25億聰,也就是25個比特幣的獎勵額。之所以採用二進位制右移操作,是因為相比於整數或浮點數除法,右移操作的效率更高。
最後,將coinbase獎勵額(變數nSubsidy
)與礦工費(nFee
)總額求和,並返回這個值。
8.5.4 創幣交易的結構
經過計算,Jing的節點構造了一個創幣交易,支付給自己25.09094928枚比特幣。
如例8-4所示,創幣交易的結構比較特殊,與一般交易輸入需要指定一個先前的UTXO不同,它包含一個“coinbase“輸入。在表5-3中,我們已經給出了交易輸入的結構。現在讓我們來比較一下常規交易輸入與創幣交易輸入。表8-1給出了常規交易輸入的結構,表8-2給出的是創幣交易輸入的結構。
表8-1 “普通“交易輸入的結構
長度 | 欄位 | 描述 |
---|---|---|
32 位元組 | 交易雜湊 | 指向包含有將要被花費UTXO的交易 |
4 位元組 | 交易輸出索引 | UTXO在交易中的索引,0 從0開始計數 |
1-9 位元組 | 解鎖指令碼長度 | 解鎖指令碼的長度 |
(VarInt) 可變長度 | Unlocking-Script | 一段指令碼,用來解鎖UTXO鎖定指令碼中的條件 |
4 bytes | 順序號 | 當前未啟用的TX替換功能,設定為0xFFFFFFFF |
表8-2 生成交易輸入的結構
長度 | 欄位 | 描述 |
---|---|---|
32 位元組 | 交易雜湊 | 不引用任何一個交易,值全部為0 |
4 位元組 | 交易輸出索引 | 值全部為1 |
1-9 位元組 | Coinbase資料長度 | coinbase資料長度 |
(VarInt) 可變長度 | Coinbase資料 | 在v2版本的區塊中,除了需要以區塊高度開始外,其他資料可以任意填寫,用於extra nonce和挖礦標籤 |
4 bytes | 順序號 | 值全部為1,0xFFFFFFFF |
在創幣交易中,“交易雜湊”欄位32個位元組全部填充0,“交易輸出索引”欄位全部填充0xFF(十進位制的255),這兩個欄位的值表示不引用UTXO。“解鎖指令碼”由coinbase資料代替,資料可以由礦工自定義。
8.5.5 Coinbase資料
創幣交易不包含“解鎖指令碼“(又稱作 scriptSig
)欄位,這個欄位被coinbase資料替代,長度最小2位元組,最大100位元組。除了開始的幾個位元組外,礦工可以任意使用coinbase的其他部分,隨意填充任何資料。
以創世塊為例,中本聰在coinbase中填入了這樣的資料“The Times 03/Jan/ 2009 Chancellor on brink of second bailout for banks“(泰晤士報 2009年1月3日 財政大臣將再次對銀行施以援手),表示對日期的證明,同時也表達了對銀行系統的不信任。現在,礦工使用coinbase資料實現extra nonce功能,並嵌入字串來標識挖出它的礦池,這部分內容會在後面的小節描述。coinbase前幾個位元組也曾是可以任意填寫的,不過在後來的第34號比特幣改進提議(BIP34)中規定了版本2的區塊(版本欄位為2的區塊),這個區塊的高度必須跟在指令碼操作“push“之後,填充在coinbase欄位的起始處。
我們以例8-4中的區塊277,316為例,coinbase就是交易輸入的“解鎖指令碼“(或scriptSig)欄位,這個欄位的十六進位制值為03443b0403858402062f503253482f。下面讓我們來解碼這段資料。
第一個位元組是03,指令碼執行引擎執行這個指令將後面3個位元組壓入指令碼棧(見表4-1),緊接著的3個位元組——0x443b04,是以小端格式(最低有效位元組在先)編碼的區塊高度。翻轉位元組序得到0x043b44,表示為十進位制是277,316。
緊接著的幾個十六進位制數(03858402062)用於編碼extra nonce(參見"8.11.1 隨機值升位方案"),或者一個隨機值,從而求解一個適當的工作量證明。
coinbase資料結尾部分(2f503253482f)是ASCII編碼字元 /P2SH/,表示挖出這個區塊的挖礦節點支援BIP0016所定義的pay-to-script-hash(P2SH)改進方案。在P2SH功能引入到比特幣的時候,曾經有過一場對P2SH不同實現方式的投票,候選者是BIP0016和BIP0017。支援BIP0016的礦工將/P2SH/放入coinbase資料中,支援BIP0017的礦工將 p2sh/CHV放入他們的coinbase資料中。最後,BIP0016在選舉中勝出,直到現在依然有很多礦工在他們的coinbase中填入/P2SH/以表示支援這個功能。
例8-6使用了libbitcoin庫(在56頁“其他替代客戶端、資料庫、工具包”中提到)從創世塊中提取coinbase資料,並顯示出中本聰留下的資訊。libbitcoin庫中自帶了一份創世塊的靜態拷貝,所以這段示例程式碼可以直接取自庫中的創世塊資料。
例8-6 從創世區塊中提取coinbase資料
/*
Display the genesis block message by Satoshi.
*/
#include <iostream>
#include <bitcoin/bitcoin.hpp>
int main()
{
// Create genesis block.
bc::block_type block = bc::genesis_block();
// Genesis block contains a single coinbase transaction. assert(block.transactions.size() == 1);
// Get first transaction in block (coinbase).
const bc::transaction_type& coinbase_tx = block.transactions[0];
// Coinbase tx has a single input.
assert(coinbase_tx.inputs.size() == 1);
const bc::transaction_input_type& coinbase_input = coinbase_tx.inputs[0];
// Convert the input script to its raw format.
const bc::data_chunk& raw_message = save_script(coinbase_input.script);
// Convert this to an std::string.
std::string message;
message.resize(raw_message.size());
std::copy(raw_message.begin(), raw_message.end(), message.begin());
// Display the genesis block message.
std::cout << message << std::endl;
return 0;
}
在例8-7中,我們使用GNU C++編譯器編譯原始碼並執行得到的可執行檔案
例8-7 編譯並執行satoshi-words示例程式碼
$ # Compile the code
$ g++ -o satoshi-words satoshi-words.cpp $(pkg-config --cflags --libs libbitcoin) $ # Run the executable
$ ./satoshi-words
^D��<GS>^A^DEThe Times 03/Jan/2009 Chancellor on brink of second bailout for banks
8.6 構造區塊頭
為了構造區塊頭,挖礦節點需要填充六個欄位,如表8-3中所示。
表8-3 區塊頭的結構
長度 | 欄位 | 描述 |
---|---|---|
4 位元組 | 版本 | 版本號,用來跟蹤軟體或協議的升級 |
32 位元組 | 前區塊雜湊 | 鏈中前一個區塊(父區塊)的雜湊值 |
32 位元組 | Merkle根 | 一個雜湊值,表示這個區塊中全部交易構成的merkle樹的根 |
4 位元組 | 時間戳 | 以Unix紀元開始到當下秒數記錄的區塊生成的時刻 |
4 bytes | 難度目標 | 該區塊的工作量證明演算法難度目標 |
4 bytes | Nonce | 一個用於工作量證明演算法的計數器 |
在區塊277,316被挖出的時候,區塊結構中用來表示版本號的欄位值為2,長度為4位元組,以小段格式編碼值為0x20000000。接著,挖礦節點需要填充“前區塊雜湊”,在本例中,這個值為Jing的節點從網路上接收到的區塊277,315的區塊頭雜湊值,它是區塊277316候選區塊的父區塊。區塊277,315的區塊頭雜湊值為:
0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569
為了向區塊頭填充merkle根欄位,要將全部的交易組成一個merkle樹。創幣交易作為區塊中的首個交易,後將餘下的418筆交易添至其後,這樣區塊中的交易一共有419筆。在164頁,我們已經見到過“Merkle樹”,樹中必須有偶數個葉子節點,所以需要複製最後一個交易作為第420個節點,每個節點是對應交易的雜湊值。這些交易的雜湊值逐層地、成對地組合,直到最終組合併成一個根節點。merkle數的根節點將全部交易資料摘要為一個32位元組長度的值,例8-3中merkel根的值如下:
c91c008c26e50763e9f548bb8b2fc323735f73577effbc55502c51eb4cc7cf2e
挖礦節點會繼續新增一個4位元組的時間戳,以Unix紀元時間編碼,即自1970年1月1日0點到當下總共流逝的秒數。本例中的1388185914對應的時間是2013年12月27日,星期五,UTC/GMT。
接下來,節點需要填充難度目標值,為了使得該區塊有效,這個欄位定義了所需滿足的工作量證明的難度。難度在區塊中以“尾數-指數”的格式,編碼並存儲,這種格式稱作“難度位”。這種編碼的首位元組表示指數,後面的3位元組表示尾數(係數)。以區塊277316為例,難度位的值為0x1903a30c,0x19是指數的十六進位制格式,後半部0x03a30c是係數。這部分的概念在第195頁的“難度目標與難度調整”和第194的“難度表示”有詳細的解釋。
最後一個欄位是nonce,初始值為0。
區塊頭完成全部的欄位填充後,挖礦就可以開始進行了。挖礦的目標是找到一個使區塊頭雜湊值小於難度目標的nonce。挖礦節點通常需要嘗試數十億甚至數萬億個不同的nonce取值,直到找到一個滿足條件的nonce值。
8.7 構建區塊
既然Jing的節點已經構建了一個候選區塊,那麼就輪到Jing的礦機對這個新區塊進行“挖掘”,求解工作量證明演算法以使這個區塊有效。從本書中我們已經學習了比特幣系統中不同地方用到的雜湊加密函式。比特幣挖礦過程使用的是SHA256雜湊函式。
用最簡單的術語來說,挖礦就是重複計算區塊頭的雜湊值,不斷修改該引數,直到與雜湊值匹配的一個過程。雜湊函式的結果無法提前得知,也沒有能得到一個特定雜湊值的模式。雜湊函式的這個特性意味著:得到雜湊值的唯一方法是不斷的嘗試,每次隨機修改輸入,直到出現適當的雜湊值。
8.7.1 工作量證明演算法
雜湊函式的輸入資料的長度是任意的,將產生一個長度固定且絕不雷同的值,可將其視為輸入的數字指紋。對於特定輸入,雜湊的結果每次都一樣,任何實現相同雜湊函式的人都可以計算和驗證。一個加密雜湊函式的主要特徵就是不同的輸入幾乎不可能出現相同的數字指紋。因此,相對於隨機選擇輸入,有意地選擇輸入去生成一個想要的雜湊值幾乎是不可能的。
無論輸入的大小是多少,SHA256函式的輸出的長度總是256bit。在例8-8中,我們將使用Python直譯器來計算語句 "I am Satoshi Nakamoto" 的SHA256的雜湊值。
例8-8 SHA256示例
$ python
Python 2.7.1
>>> import hashlib
>>> print hashlib.sha256("I am Satoshi Nakamoto").hexdigest() 5d7c7ba21cbbcd75d14800b100252d5b428e5b1213d27c385bc141ca6b47989e
在例8-8中,5d7c7ba21cbbcd75d14800b100252d5b428e5b1213d27c385bc141ca6b47989e
是"I am Satoshi Nakamoto"的雜湊值。改變原句中的任何一個字母、標點、或增加字母都會產生不同的雜湊值。
如果我們改變原句,得到的應該是完全不同的雜湊值。例如,我們在句子末尾加上一個數字,執行例8-9中的Python指令碼。
例8-9 通過迭代 nonce 來生成不同雜湊值的指令碼(SHA256)
# example of iterating a nonce in a hashing algorithm's input
import hashlib
text = "I am Satoshi Nakamoto"
# iterate nonce from 0 to 19
for nonce in xrange(20):
# add the nonce to the end of the text
input = text + str(nonce)
# calculate the SHA-256 hash of the input (text+nonce)
hash = hashlib.sha256(input).hexdigest() # show the input and hash result
print input, '=>', hash
執行這個指令碼就能生成這些只是末尾數字不同的語句的雜湊值。例8-10 中顯示了我們只是增加了這個數字,卻得到了非常不同的雜湊值。
例8-10 通過迭代 nonce 來生成不同雜湊值的指令碼的輸出
$ python hash_example.py
I am Satoshi Nakamoto0 => a80a81401765c8eddee25df36728d732...
I am Satoshi Nakamoto1 => f7bc9a6304a4647bb41241a677b5345f...
I am Satoshi Nakamoto2 => ea758a8134b115298a1583ffb80ae629...
I am Satoshi Nakamoto3 => bfa9779618ff072c903d773de30c99bd...
I am Satoshi Nakamoto4 => bce8564de9a83c18c31944a66bde992f...
I am Satoshi Nakamoto5 => eb362c3cf3479be0a97a20163589038e...
I am Satoshi Nakamoto6 => 4a2fd48e3be420d0d28e202360cfbaba...
I am Satoshi Nakamoto7 => 790b5a1349a5f2b909bf74d0d166b17a...
I am Satoshi Nakamoto8 => 702c45e5b15aa54b625d68dd947f1597...
I am Satoshi Nakamoto9 => 7007cf7dd40f5e933cd89fff5b791ff0...
I am Satoshi Nakamoto10 => c2f38c81992f4614206a21537bd634a...
I am Satoshi Nakamoto11 => 7045da6ed8a914690f087690e1e8d66...
I am Satoshi Nakamoto12 => 60f01db30c1a0d4cbce2b4b22e88b9b...
I am Satoshi Nakamoto13 => 0ebc56d59a34f5082aaef3d66b37a66...
I am Satoshi Nakamoto14 => 27ead1ca85da66981fd9da01a8c6816...
I am Satoshi Nakamoto15 => 394809fb809c5f83ce97ab554a2812c...
I am Satoshi Nakamoto16 => 8fa4992219df33f50834465d3047429...
I am Satoshi Nakamoto17 => dca9b8b4f8d8e1521fa4eaa46f4f0cd...
I am Satoshi Nakamoto18 => 9989a401b2a3a318b01e9ca9a22b0f3...
I am Satoshi Nakamoto19 => cda56022ecb5b67b2bc93a2d764e75f...
每個語句都生成了一個完全不同的雜湊值。它們看起來是完全隨機的,但你在任何計算機上用Python執行上面的指令碼都能重現這些完全相同的雜湊值。
類似這樣在語句末尾的變化的數字叫做nonce。Nonce是用來改變加密函式輸出的,在這個示例中改變了這個語句的SHA256指紋。
為了使這個雜湊演算法變得富有挑戰,我們來設定一個具有任意性的目標:找到一個語句,使之雜湊值的十六進位制表示以0開頭。幸運的是,這很容易!在例8-10中語句 "I am Satoshi Nakamoto13" 的雜湊值是 0ebc56d59a34f5082aaef3d66b37a661696c2b618e62432727216ba9531041a5
,剛好滿足條件。我們得到它用了13次。用概率的角度來看,如果雜湊函式的輸出是平均分佈的,我們可以期望每16次得到一個以0開頭的雜湊值(十六進位制每一位數字為0到F)。從數字的角度來看,我們要找的是小於
0x1000000000000000000000000000000000000000000000000000000000000000的雜湊值。我們稱這個為目標閥值,我們的目的是找到一個小於這個目標的雜湊值。如果我們減小這個目標值,那找到一個小於它的雜湊值會越來越難。
簡單打個比方,想象人們不斷扔一對色子以得到小於一個特定點數的遊戲。第一局,目標是12。只要你不扔出兩個6,你就會贏。然後下一局目標為11。玩家只能扔10或更小的點數才能贏,不過也很簡單。假如幾局之後目標降低為了5。現在有一半機率以上扔出來的色子加起來點數會超過5,因此無效。隨著目標越來越小,要想贏的話,扔色子的次數會指數級的上升。最終當目標為2時(最小可能點數),只有一個人平均扔36次或2%扔的次數中,他才能贏。
在例8-10中,成功的nonce為13,且這個結果能被所有人獨立確認。任何人將13加到語句 "I am Satoshi Nakamoto" 後面再計算雜湊值都能確認它比目標值要小。這個正確的結果同時也是工作量證明(Proof of Work),因為它證明我們的確花時間找到了這個nonce。驗證這個雜湊值只需要一次計算,而我們找到它卻花了13次。如果目標值更小(難度更大),那我們需要多得多的雜湊計算才能找到合適的nonce,但其他人驗證它時只需要一次雜湊計算。此外,知道目標值後,任何人都可以用統計學來估算其難度,因此就能知道找到這個nonce需要多少工作。
比特幣的工作量證明和例8-10中的挑戰非常類似。礦工用一些交易構建一個候選區塊。接下來,這個礦工計算這個區塊頭資訊的雜湊值,看其是否小於當前目標值。如果這個雜湊值不小於目標值,礦工就會修改這個nonce(通常將之加1)然後再試一次。按當前比特幣系統的難度,礦工得試10^15次(10的15次方)才能找到一個合適的nonce使區塊頭資訊雜湊值足夠小。
例8-11是一個簡化很多的工作量證明演算法的實現。
例8-11 簡化的工作量證明演算法
#!/usr/bin/env python
# example of proof-of-work algorithm
import hashlib
import time
max_nonce = 2 ** 32 # 4 billion
def proof_of_work(header, difficulty_bits):
# calculate the difficulty target
target = 2 ** (256-difficulty_bits)
for nonce in xrange(max_nonce):
hash_result = hashlib.sha256(str(header)+str(nonce)).hexdigest()
# check if this is a valid result, below the target
if long(hash_result, 16) < target:
print "Success with nonce %d" % nonce
print "Hash is %s" % hash_result
return (hash_result,nonce)
print "Failed after %d (max_nonce) tries" % nonce
return nonce
if __name__ == '__main__':
nonce = 0
hash_result = ''
# difficulty from 0 to 31 bits
for difficulty_bits in xrange(32):
difficulty = 2 ** difficulty_bits
print "Difficulty: %ld (%d bits)" % (difficulty, difficulty_bits)
print "Starting search..."
# checkpoint the current time
start_time = time.time()
# make a new block which includes the hash from the previous block
# we fake a block of transactions - just a string
new_block = 'test block with transactions' + hash_result
# find a valid nonce for the new block
(hash_result, nonce) = proof_of_work(new_block, difficulty_bits)
# checkpoint how long it took to find a result
end_time = time.time()
elapsed_time = end_time - start_time
print "Elapsed Time: %.4f seconds" % elapsed_time
if elapsed_time > 0:
# estimate the hashes per second
hash_power = float(long(nonce)/elapsed_time)
print "Hashing Power: %ld hashes per second" % hash_power
你可以任意調整難度值(按二進位制bit數來設定,即雜湊值開頭多少個bit必須是0)。然後執行程式碼,看看在你的計算機上求解需要多久。在例8-12中,你可以看到該程式在一個普通膝上型電腦上的執行情況。
例8-12 多種難度值的工作量證明演算法的執行輸出
$ python proof-of-work-example.py*
Difficulty: 1 (0 bits)
[...]
Difficulty: 8 (3 bits)
Starting search...
Success with nonce 9
Hash is 1c1c105e65b47142f028a8f93ddf3dabb9260491bc64474738133ce5256cb3c1
Elapsed Time: 0.0004 seconds
Hashing Power: 25065 hashes per second
Difficulty: 16 (4 bits)
Starting search...
Success with nonce 25
Hash is 0f7becfd3bcd1a82e06663c97176add89e7cae0268de46f94e7e11bc3863e148
Elapsed Time: 0.0005 seconds
Hashing Power: 52507 hashes per second
Difficulty: 32 (5 bits)
Starting search...
Success with nonce 36
Hash is 029ae6e5004302a120630adcbb808452346ab1cf0b94c5189ba8bac1d47e7903
Elapsed Time: 0.0006 seconds
Hashing Power: 58164 hashes per second
[...]
Difficulty: 4194304 (22 bits)
Starting search...
Success with nonce 1759164
Hash is 0000008bb8f0e731f0496b8e530da984e85fb3cd2bd81882fe8ba3610b6cefc3
Elapsed Time: 13.3201 seconds
Hashing Power: 132068 hashes per second
Difficulty: 8388608 (23 bits)
Starting search...
Success with nonce 14214729
Hash is 000001408cf12dbd20fcba6372a223e098d58786c6ff93488a9f74f5df4df0a3
Elapsed Time: 110.1507 seconds
Hashing Power: 129048 hashes per second
Difficulty: 16777216 (24 bits)
Starting search...
Success with nonce 24586379
Hash is 0000002c3d6b370fccd699708d1b7cb4a94388595171366b944d68b2acce8b95
Elapsed Time: 195.2991 seconds
Hashing Power: 125890 hashes per second
[...]
Difficulty: 67108864 (26 bits)
Starting search...
Success with nonce 84561291
Hash is 0000001f0ea21e676b6dde5ad429b9d131a9f2b000802ab2f169cbca22b1e21a
Elapsed Time: 665.0949 seconds
Hashing Power: 127141 hashes per second
你可以看出,隨著難度位一位一位地增加,查詢正確結果的時間會呈指數級增長。如果你考慮整個256bit數字空間,每次要求多一個0,你就把雜湊查詢空間縮減了一半。在例8-12中,為尋找一個nonce使得雜湊值開頭的26位值為0,一共嘗試了8千多萬次。即使家用筆記本每秒可以達270,000多次雜湊計算,這個查詢依然需要6分鐘。
在寫這本書的時候,比特幣網路要尋找區塊頭資訊雜湊值小於 000000000000004c296e6376db3a241271f43fd3f5de7ba18986e517a243baa7。可以看出,這個目標雜湊值開頭的0多了很多。這意味著可接受的雜湊值範圍大幅縮減,因而找到正確的雜湊值更加困難。生成下一個區塊需要網路每秒計算1.5 x 1017次雜湊。這看起來像是不可能的任務,但幸運的是比特幣網路已經擁有100PH每秒(petahashes per second, peta-為 1015)的處理能力,平均每10分鐘就可以找到一個新區塊。
8.7.2 難度表示
在例8-3中,我們在區塊中看到難度目標,其被標為"難度位"或簡稱"bits"。在區塊277,316中,它的值為 0x1903a30c。這個標記的值被存為係數/指數格式,前兩位十六進位制數字為冪,接下來得六位為係數。在這個區塊裡,0x19為冪,而0x03a30c為係數。
計算難度目標的公式為:
target = coefficient * 2^(8 * (exponent – 3))
由此公式及難度位的值 0x1903a30c,可得:
target = 0x03a30c * 2^(0x08 * (0x19 - 0x03))
=> target = 0x03a30c * 2^(0x08 * 0x16)
=> target = 0x03a30c * 2^0xB0
按十進位制計算為:
=> target = 238,348 * 2^176
=> target =
22,829,202,948,393,929,850,749,706,076,701,368,331,072,452,018,388,575,715,328
轉化為十六進位制後為:
=> target =0x0000000000000003A30C00000000000000000000000000000000000000000000
也就是說高度為277,316的有效區塊的頭資訊雜湊值是小於這個目標值的。這個數字的二進位制表示中前60位都是0。在這個難度上,一個每秒可以處理1萬億個雜湊計算的礦工(1 tera-hash per second 或 1 TH/sec)平均每8,496個區塊才能找到一個正確結果,換句話說,平均每59天,才能為某一個區塊找到正確的雜湊值。
8.7.3 難度目標與難度調整
如前所述,目標決定了難度,進而影響求解工作量證明演算法所需要的時間。那麼問題來了:為什麼這個難度值是可調整的?由誰來調整?如何調整?
比特幣的區塊平均每10分鐘生成一個。這就是比特幣的心跳,是貨幣發行速率和交易達成速度的基礎。不僅是在短期內,而是在幾十年內它都必須要保持恆定。在此期間,計算機效能將飛速提升。此外,參與挖礦的人和計算機也會不斷變化。為了能讓新區塊的保持10分鐘一個的產生速率,挖礦的難度必須根據這些變化進行調整。事實上,難度是一個動態的引數,會定期調整以達到每10分鐘一個新區塊的目標。簡單地說,難度被設定在,無論挖礦能力如何,新區塊產生速率都保持在10分鐘一個。
那麼,在一個完全去中心化的網路中,這樣的調整是如何做到的呢?難度的調整是在每個完整節點中獨立自動發生的。每2,016個區塊中的所有節點都會調整難度。難度的調整公式是由最新2,016個區塊的花費時長與20,160分鐘(兩週,即這些區塊以10分鐘一個速率所期望花費的時長)比較得出的。難度是根據實際時長與期望時長的比值進行相應調整的(或變難或變易)。簡單來說,如果網路發現區塊產生速率比10分鐘要快時會增加難度。如果發現比10分鐘慢時則降低難度。
這個公式可以總結為如下形式:
New Difficulty = Old Difficulty * (Actual Time of Last 2016 Blocks / 20160 minutes)
例8-13展示了比特幣核心客戶端中的難度調整程式碼。
例8-13 工作量證明的難度調整 原始檔 pow.cpp 第43行函式 GetNextWorkRequired()
// Go back by what we want to be 14 days worth of blocks
const CBlockIndex* pindexFirst = pindexLast;
for (int i = 0; pindexFirst && i < Params().Interval()-1; i++)
pindexFirst = pindexFirst->pprev;
assert(pindexFirst);
// Limit adjustment step
int64_t nActualTimespan = pindexLast->GetBlockTime() - pindexFirst->GetBlockTime(); LogPrintf(" nActualTimespan = %d before bounds\n", nActualTimespan);
if (nActualTimespan < Params().TargetTimespan()/4)
nActualTimespan = Params().TargetTimespan()/4;
if (nActualTimespan > Params().TargetTimespan()*4)
nActualTimespan = Params().TargetTimespan()*4;
// Retarget
uint256 bnNew;
uint256 bnOld;
bnNew.SetCompact(pindexLast->nBits);
bnOld = bnNew;
bnNew *= nActualTimespan;
bnNew /= Params().TargetTimespan();
if (bnNew > Params().ProofOfWorkLimit())
bnNew = Params().ProofOfWorkLimit();
引數Interval(2,016區塊)和TargetTimespan(1,209,600秒即兩週) 的定義在檔案chainparams.cpp中。
為了防止難度的變化過快,每個週期的調整幅度必須小於一個因子(值為4)。如果要調整的幅度大於4倍,則按4倍調整。由於在下一個2,016區塊的週期不平衡的情況會繼續存在,所以進一步的難度調整會在下一週期進行。因此平衡雜湊計算能力和難度的巨大差異有可能需要花費幾個2,016區塊週期才會完成。
尋找一個比特幣區塊需要整個網路花費10分鐘來處理,每發現2,016個區塊時會根據前2,016個區塊完成的時間對難度進行調整。
值得注意的是目標難度與交易的數量和金額無關。這意味著雜湊算力的強弱,即讓比特幣更安全的電力投入量,與交易的數量完全無關。換句話說,當比特幣的規模變得更大,使用它的人數更多時,即使雜湊算力保持當前的水平,比特幣的安全性也不會受到影響。雜湊算力的增加表明更多的人為得到比特幣回報而加入了挖礦隊伍。只要為了回報,公平正當地從事挖礦的礦工群體保持足夠的雜湊算力,"接管"攻擊就不會得逞,讓比特幣的安全無虞。
目標難度和挖礦電力消耗與將比特幣兌換成現金以支付這些電力之間的關係密切相關。高效能挖礦系統就是要用當前矽晶片以最高效的方式將電力轉化為雜湊算力。挖礦市場的關鍵因素就是每度電轉換為比特幣後的價格。因為這決定著挖礦活動的營利性,也因此刺激著人們選擇進入或退出挖礦市場。
8.8 成功構建區塊
前面已經看到,Jing的節點建立了一個候選區塊,準備拿它來挖礦。Jing有幾個安裝了ASIC(專用積體電路)的礦機,上面有成千上萬個積體電路可以超高速地並行執行SHA256演算法。這些定製的硬體通過USB連線到他的挖礦節點上。接下來,執行在Jing的桌面電腦上的挖礦節點將區塊頭資訊傳送給這些硬體,讓它們以每秒億萬次的速度進行nonce測試。
在對區塊277,316的挖礦工作開始大概11分鐘後,這些硬體裡的其中一個求得了解併發回挖礦節點。當把這個結果放進區塊頭時,nonce 4,215,469,401 就會產生一個區塊雜湊值:
0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569
而這個值小於難度目標值:
0000000000000003A30C00000000000000000000000000000000000000000000
Jing的挖礦節點立刻將這個區塊發給它的所有相鄰節點。這些節點在接收並驗證這個新區塊後,也會繼續傳播此區塊。當這個新區塊在網路中擴散時,每個節點都會將它作為區塊277,316加到自身節點的區塊鏈副本中。當挖礦節點收到並驗證了這個新區塊後,它們會放棄之前對構建這個相同高度區塊的計算,並立即開始計算區塊鏈中下一個區塊的工作。
下節將介紹節點進行區塊驗證、最長鏈選擇、達成共識,並以此形成一個去中心化區塊鏈的過程。
8.9 校驗新區塊
比特幣共識機制的第三步是通過網路中的每個節點獨立校驗每個新區塊。當新區塊在網路中傳播時,每一個節點在將它轉發到其節點之前,會進行一系列的測試去驗證它。這確保了只有有效的區塊會在網路中傳播。獨立校驗還確保了誠實的礦工生成的區塊可以被納入到區塊鏈中,從而獲得獎勵。行為不誠實的礦工所產生的區塊將被拒絕,這不但使他們失去了獎勵,而且也浪費了本來可以去尋找工作量證明解的機會,因而導致其電費虧損。
當一個節點接收到一個新的區塊,它將對照一個長長的標準清單對該區塊進行驗證,若沒有通過驗證,這個區塊將被拒絕。這些標準可以在比特幣核心客戶端的CheckBlock函式和CheckBlockHead函式中獲得,它包括:
▷ 區塊的資料結構語法上有效
▷ 區塊頭的雜湊值小於目標難度(確認包含足夠的工作量證明)
▷ 區塊時間戳早於驗證時刻未來兩個小時(允許時間錯誤)
▷ 區塊大小在長度限制之內
▷ 第一個交易(且只有第一個)是coinbase交易
▷ 使用檢查清單驗證區塊內的交易並確保它們的有效性,本書177頁
▷ “交易的獨立校驗”一節已經討論過這個清單。
每一個節點對每一個新區塊的獨立校驗,確保了礦工無法欺詐。在前面的章節中,我們看到了礦工們如何去記錄一筆交易,以獲得在此區塊中創造的新比特幣和交易費。為什麼礦工不為他們自己記錄一筆交易去獲得數以千計的比特幣?這是因為每一個節點根據相同的規則對區塊進行校驗。一個無效的coinbase交易將使整個區塊無效,這將導致該區塊被拒絕,因此,該交易就不會成為總賬的一部分。礦工們必須構建一個完美的區塊,基於所有節點共享的規則,並且根據正確工作量證明的解決方案進行挖礦,他們要花費大量的電力挖礦才能做到這一點。如果他們作弊,所有的電力和努力都會浪費。這就是為什麼獨立校驗是去中心化共識的重要組成部分。
8.10 區塊鏈的組裝與選擇
比特幣去中心化的共識機制的最後一步是將區塊集合至有最大工作量證明的鏈中。一旦一個節點驗證了一個新的區塊,它將嘗試將新的區塊連線到到現存的區塊鏈,將它們組裝起來。
節點維護三種區塊:第一種是連線到主鏈上的,第二種是從主鏈上產生分支的(備用鏈),最後一種是在已知鏈中沒有找到已知父區塊的。在驗證過程中,一旦發現有不符合標準的地方,驗證就會失敗,這樣區塊會被節點拒絕,所以也不會加入到任何一條鏈中。
任何時候,主鏈都是累計了最多難度的區塊鏈。在一般情況下,主鏈也是包含最多區塊的那個鏈,除非有兩個等長的鏈並且其中一個有更多的工作量證明。主鏈也會有一些分支,這些分支中的區塊與主鏈上的區塊互為“兄弟”區塊。這些區塊是有效的,但不是主鏈的一部分。 保留這些分支的目的是如果在未來的某個時刻它們中的一個延長了並在難度值上超過了主鏈,那麼後續的區塊就會引用它們。在“8.10.1 區塊鏈分叉”,我們將會看到在同樣的區塊高度,幾乎同時挖出區塊時,候選鏈是如何產生的。
當節點接收到新區塊,它會嘗試將這個區塊插入到現有區塊鏈中。節點會看一下這個區塊的“previous block hash”欄位,這個欄位是該區塊對其父區塊的引用。同時,新的節點將嘗試在已存在的區塊鏈中找出這個父區塊。大多數情況下,父區塊是主塊鏈的“頂點”,這就意味著這個新的區塊延長了主鏈。舉個例子,一個新的區塊——區塊277,316引用了它的父區塊——區塊277,315。大部分收到了區塊277,316的節點將區塊277,315作為主鏈的頂點,連線這個新區塊並延長區塊鏈。
有時候,新區塊所延長的區塊鏈並不是主鏈,這一點我們將在“8.10.1 區塊鏈分叉”中看到。在這種情況下,節點將新的區塊新增到備用鏈,同時比較備用鏈與主鏈的難度。如果備用鏈比主鏈積累了更多的難度,節點將收斂於備用鏈,意味著節點將選擇備用鏈作為其新的主鏈,而之前那個老的主鏈則成為了備用鏈。如果節點是一個礦工,它將開始構造新的區塊,來延長這個更新更長的區塊鏈。
如果節點收到了一個有效的區塊,而在現有的區塊鏈中卻未找到它的父區塊,那麼這個區塊被認為是“孤塊”。孤塊會被儲存在孤塊池中,直到它們的父區塊被節點收到。一旦收到了父區塊並且將其連線到現有區塊鏈上,節點就會將孤塊從孤塊池中取出,並且連線到它的父區塊,讓它作為區塊鏈的一部分。當兩個區塊在很短的時間間隔內被挖出來,節點有可能會以相反的順序接收到它們,這個時候孤塊現象就會出現。
選擇了最大難度的區塊鏈後,所有的節點最終在全網範圍內達成共識。隨著更多的工作量證明被新增到鏈中,鏈的暫時性差異最終會得到解決。挖礦節點通過“投票”來選擇它們想要延長的區塊鏈,當它們挖出一個新塊並且延長了一個鏈,新塊本身就代表它們的投票。
相互競爭的鏈之間是存在差異的,下節我們將看到節點是怎樣通過獨立選擇最長難度鏈來解決這種差異的。
8.10.1 區塊鏈分叉
因為區塊鏈是去中心化的資料結構,所以不同副本之間不能總是保持一致。區塊有可能在不同時間到達不同節點,導致節點有不同的區塊鏈視角。解決的辦法是,每一個節點總是選擇並嘗試延長代表累計了最大工作量證明的區塊鏈,也就是最長的或最大累計難度的鏈。節點通過將記錄在每個區塊中的難度加總起來,得到建立這個鏈所要付出的工作量證明的總量。只要所有的節點選擇最長累計難度的區塊鏈,整個比特幣網路最終會收斂到一致的狀態。分叉即在不同區塊鏈間發生的臨時差異,當更多的區塊新增到了某個分叉中,這個問題便會迎刃而解。
在下面的圖例中,我們可以瞭解網路中發生分叉的過程。圖例代表簡單的全球比特幣網路,在真實的情況下,比特幣網路的拓撲結構不是基於地理位置組織起來的。相反,在同一個網路中相互連線的節點,可能在地理位置上相距遙遠,我們採用基於地理的拓撲是為了更加簡潔地描述分叉。在真實比特幣網路裡,節點間的距離按“跳”而不是按照真實位置來衡量。為了便於描述,不同的區塊被標示為不同的顏色,傳播這些區塊的節點網路也被標上顏色。
在第一張圖(圖8-2)中,網路有一個統一的區塊鏈視角,以藍色區塊為主鏈的“頂點”。
圖8-2 形象化的區塊鏈分叉事件——分叉之前
當有兩個候選區塊同