精通比特幣(第十章)【挖礦和共識】

風靈使發表於2018-10-09

10.1 簡介

“挖礦”這個詞有點誤導。 一般意義的挖礦類似貴金屬的提取,更多將人們的注意力集中到創造每個區塊中獲得的獎勵。 雖然挖礦能夠獲得這種獎勵作為激勵,但挖礦的主要目的不是這個獎勵或者產生新幣。 如果您只是把挖礦視為建立新幣的過程,則會將比特幣系統中的這個手段(激勵)作為挖礦過程的目標。 挖礦最重要的作用是鞏固了去中心化的清算交易機制,通過這種機制,交易得到驗證和清算//清除。 挖礦是使得比特幣與眾不同的發明,它實現去中心化的安全機制,是P2P數字貨幣的基礎。

​挖礦確保了比特幣系統安全,並且在沒有中央權力機構的情況下實現了全網路範圍的共識。 新幣發行和交易費的獎勵是將礦工的行動與網路安全保持一致的激勵計劃,同時實現了貨幣發行。

提示:挖礦的目的不是創造新的比特幣。 這是激勵機制。 挖礦是一種機制,這種機制實現了去中心化的安全。

​礦工們驗證每筆新的交易並把它們記錄在總帳簿上。每10分鐘就會有一個新的區塊被“挖掘”出來,每個區塊裡包含著從 上一個區塊產生到目前這段時間內發生的所有交易,這些交易被依次新增到區塊鏈中。我們把包含在區塊內且被新增到 區塊鏈上的交易稱為“確認”(confirmed)交易,交易經過“確認”之後,新的擁有者才能夠花費他在交易中得到的比特幣。

礦工們在挖礦過程中會得到兩種型別的獎勵:建立新區塊的新幣獎勵,以及區塊中所含交易的交易費。為了得到這些獎 勵,礦工們爭相完成一種基於加密雜湊演算法的數學難題,這些難題的答案包括在新區塊中,作為礦工的計算工作量的證 明,被稱為”“工作量證明”。該演算法的競爭機制以及獲勝者有權在區塊鏈上進行交易記錄的機制,這二者是比特幣安全的基石。

新比特幣的生成過程被稱為挖礦,是因為它的獎勵機制被設計為速度遞減模式,類似於貴重金屬的挖礦過程。比特幣的貨幣是通過挖礦發行的,類似於中央銀行通過印刷銀行紙幣來發行貨幣。礦工通過創造一個新區塊得到的比特幣數量大約 每四年(或準確說是每210,000個塊)減少一半。開始時為2009年1月每個區塊獎勵50個比特幣,然後到2012年11月減半為每個區塊獎勵25個比特幣。之後在2016年7月 再次減半為每個新區塊獎勵12.5個比特幣。基於這個公式,比特幣挖礦獎勵以指數方式遞減,直到2140年。屆時所有的比特幣(20,999,999,980)全部發行完畢。換句話說 在2140年之後,不會再有新的比特幣產生。

礦工們同時也會獲取交易費。每筆交易都可能包含一筆交易費,交易費是每筆交易記錄的輸入和輸出的差額。在挖礦過程中成功“挖出”新區塊的礦工可以得到該區塊中包含的所有交易“小費”。目前,這筆費用佔礦工收入的0.5%或更少,大 部分收益仍來自挖礦所得的比特幣獎勵。然而隨著挖礦獎勵的遞減,以及每個區塊中包含的交易數量增加,交易費在礦 工收益中所佔的比重將會逐漸增加。在2140年之後,所有的礦工收益都將由交易費構成。

在本章中,我們先來審視比特幣的貨幣發行機制,然後再來了解挖礦的最重要的功能:支撐比特幣安全的去中心化的共識機制。

​為了瞭解挖礦和共識,我們將跟隨Alice的交易,以及上海的礦工Jing如何收到並利用挖礦裝置將交易加入區塊。 然後,我們將繼續跟蹤區塊被挖礦,加入區塊鏈,並通過共識被比特幣網路接受。

10.1.1 比特幣經濟學和貨幣創造

​通過創造出新區塊,比特幣以一個確定的但不斷減慢的速率被鑄造出來。大約每十分鐘產生一個新區塊,每一個新區塊 都伴隨著一定數量從無到有的全新比特幣。每開採210,000個塊,大約耗時4年,貨幣發行速率降低50%。在比特幣運 行的第一個四年中,每個區塊創造出50個新比特幣。

2012年11月,比特幣的新發行速度降低到每區塊25個比特幣。2016年7月,降低到12.5比特幣/區塊。2020年的某個時候,也就是在區塊630,000,它將再次下降至6.25比特幣。新幣的發行速度會以指數級進行32次“等分”,直到第6,720,000塊(大約在2137年開採),達到比特幣的最小貨幣單位1 satoshi。最終,在經過693萬個區塊之後,所有的共 2,099,999,997,690,000聰比特幣將全部發行完畢。也就是說,到2140年左右,會存在接近 2,100萬比特幣。在那之後,新的區塊不再包含比特幣獎勵,礦工的收益全部來自交易費。圖10-1展示了在發行速度不斷降低的情況下,比特幣總流通量與時間的關係。

在這裡插入圖片描述

​ 圖10-1 比特幣發行與時間的關係

注意:比特幣挖礦發行的最大數量也就成為挖礦獎勵的上限。 在實踐中,礦工可能故意挖掘哪些不足全額獎勵的區塊。 這些塊已經開採了,未來可能會有更多被開採,這樣導致貨幣發行總量的下降。

​ 在例10-1的程式碼展示中,我們計算了比特幣的總髮行量

​ 例10-1 比特幣發行總量的計算指令碼

code/max_money.py[]

Running the max_money.py script說明了執行指令碼的輸出結果

例10-2 顯示上述指令碼的輸出

$ python max_money.pyTotal BTC to ever be created: 2099999997690000 Satoshis

總量有限並且發行速度遞減創造了一種抗通脹的貨幣供應模式。法幣可被中央銀行無限制地印刷出來,而比特幣永遠不會因超額印發而出現通脹。

通貨緊縮

最重要並且最有爭議的一個結論是一種事先確定的發行速率遞減的貨幣發行模式會導致貨幣通貨緊縮(簡稱通縮)。通縮是一種由於貨幣的供應和需求不匹配導致的貨幣增值的現象。它與通脹相反,價格通縮意味著貨幣隨著時間有越來越強的購買力。

許多經濟學家提出通縮經濟是一種無論如何都要避免的災難型經濟。因為在快速通縮時期,人們預期著商品價格會下跌,人們將會儲存貨幣,避免花掉它。這種現象充斥了日本經濟“失去的十年”,就是因為在需求坍塌之後導致了滯漲狀態。

比特幣專家們認為通縮本身並不壞。更確切地說,我們將通縮與需求坍塌聯絡在一起是因為過去出現的一個特例。在法幣屆,貨幣是有可能被無限制印刷出來的,除非遇到需求完全崩塌並且毫無發行貨幣意願的情形,因此經濟很難進入滯漲期。而比特幣的通縮並不是需求坍塌引起的,它遵循一種預定且有節制的貨幣供應模型。

​通貨緊縮的積極因素當然是與通貨膨脹相反。 通貨膨脹導致貨幣緩慢但不可避免的貶值,這是一種隱性稅收的形式,懲罰在銀行存錢的人從而實現解救債務人(包括政府這個最大的債務人)。 政府控制下的貨幣容易遭受債務發行的道德風險,之後可能會以犧牲儲蓄者為代價,通過貶值來抹去債務。

比特幣這種不是因經濟快速衰退而引起的通縮,是否會引發其他問題,仍有待觀察。

10.2 去中心化共識

​在上一章中我們瞭解了區塊鏈。可以將區塊鏈看作一本記錄所有交易的公開總帳簿(列表),比特幣網路中的每個參與 者都把能接受區塊鏈,把它看作一本證明所有權的權威記錄。

​但在不考慮相信任何人的情況下,比特幣網路中的所有參與者如何達成對任意一個所有權的共識呢?所有的傳統支付系 統都依賴於一箇中心認證機構,依靠中心機構提供的結算服務來驗證並處理所有的交易。比特幣沒有中心機構,幾乎所 有的完整節點都有一份公共總帳的備份,這份總帳可以被視為權威記錄。區塊鏈並不是由一箇中心機構創造的,它是由比特幣網路中的所有節點各自獨立競爭完成的。換句話說比特幣網路中的所有節點,依靠著節點間的不穩定的網路連線所傳輸的資訊,最終得出同樣的結果並維護了同一個公共總帳。這一章將介紹比特幣網路不依靠中心機構而達成共識的機制。

​中本聰的主要發明就是這種去中心化的自發共識(emergent consensus)機制。這種自發,是指共識沒有明確的完成點,因為共識達成時,沒有明確的選舉和固定時刻。換句話說,共識是數以千計的獨立節點遵守了簡單的規則通過非同步互動自發形成的產物。所有的比特幣屬性,包括貨幣、交易、支付以及不依靠中心機構和信任的安全模型等都依賴於這個發明。

比特幣的去中心化共識由所有網路節點的4種獨立過程相互作用而產生:

▷ 每個全節點依據綜合標準對每個交易進行獨立驗證

▷ 通過完成工作量證明演算法的驗算,挖礦節點將交易記錄獨立打包進新區塊

▷ 每個節點獨立的對新區塊進行校驗並組裝進區塊鏈

▷ 每個節點對區塊鏈進行獨立選擇,在工作量證明機制下選擇累計工作量最大的區塊鏈。

在接下來的幾節中,我們將審視這些過程,瞭解它們之間如何相互作用並達成全網的自發共識,從而使任意節點組合出 它自己的權威、可信、公開的總帳副本。

10.3 交易的獨立校驗

​在第6章交易中,我們知道了錢包軟體通過收集UTXO、提供正確的解鎖指令碼、構造一個新的支出(支付)給接收者這一系列的方式來建立交易。產生的交易隨後將被髮送到比特幣網路臨近的節點,從而使得該交易能夠在整個比特幣網路中傳播。

​然而,在交易傳遞到臨近的節點前,每一個收到交易的比特幣節點將會首先驗證該交易,這將確保只有有效的交易才會 在網路中傳播,而無效的交易將會在第一個節點處被廢棄。

每一個節點在校驗每一筆交易時,都需要對照一個長長的標準列表:

▷交易的語法和資料結構必須正確。

▷輸入與輸出列表都不能為空。

▷交易的位元組大小是小於 MAX_BLOCK_SIZE 的。

▷每一個輸出值,以及總量,必須在規定值的範圍內 (小於2,100萬個幣,大於0)。

▷沒有雜湊等於0,N等於-1的輸入(coinbase交易不應當被傳遞)。

▷nLockTime是小於或等於 INT_MAX 的。或者nLocktime and nSequence的值滿足MedianTimePast(譯者注:MedianTime是這個塊的前面11個塊按照block time排序後的中間時間)

▷交易的位元組大小是大於或等於100的。

▷交易中的簽名數量(SIGOPS)應小於簽名運算元量上限。

▷解鎖指令碼( scriptSig )只能夠將數字壓入棧中,並且鎖定指令碼( scriptPubkey )必須要符合isStandard的格式 (該格式將會拒絕非標準交易)。

▷池中或位於主分支區塊中的一個匹配交易必須是存在的。

▷對於每一個輸入,引用的輸出是必須存在的,並且沒有被花費。

▷對於每一個輸入,如果引用的輸出存在於池中任何別的交易中(譯者注:這筆輸入引用的輸出有人家自己的輸入,不是你),該交易將被拒絕。

▷對於每一個輸入,在主分支和交易池中尋找引用的輸出交易。如果輸出交易缺少任何一個輸入,該交易將成為一個孤 立的交易。如果與其匹配的交易還沒有出現在池中,那麼將被加入到孤立交易池中。

▷對於每一個輸入,如果引用的輸出交易是一個coinbase輸出,該輸入必須至少獲得 COINBASE_MATURITY(100)個確認。

▷使用引用的輸出交易獲得輸入值,並檢查每一個輸入值和總值是否在規定值的範圍內 (小於2100萬個幣,大於0)。

▷如果輸入值的總和小於輸出值的總和,交易將被中止。

▷如果交易費用太低以至於無法進入一個空的區塊,交易將被拒絕。

▷每一個輸入的解鎖指令碼必須依據相應輸出的鎖定指令碼來驗證。

​這些條件能夠在比特幣標準客戶端下的 AcceptToMemoryPool 、 CheckTransaction 和 CheckInputs 函式中獲得更詳細的闡述。 請注意,這些條件會隨著時間發生變化,為了處理新型拒絕服務攻擊,有時候也為交易型別多樣化而放寬規則。

​在收到交易後,每一個節點都會在全網廣播前對這些交易進行獨立校驗,並以接收時的相應順序,為有效的新交易建立一個驗證池(還未確認),這個池可以叫做交易池,或者memory pool或者mempool。

10.4 挖礦節點

在比特幣網路中,一些節點被稱為專業節點“礦工”。第1章中,我們介紹了Jing,在中國上海的計算機工程專業學生,他 就是一位礦工。Jing通過礦機挖礦獲得比特幣,礦機是專門設計用於挖比特幣的計算機硬體系統。Jing的這臺專業挖礦裝置連線著一個執行完整比特幣節點的伺服器。與Jing不同,一些礦工是在沒有完整節點的條件下進行挖礦,正如我們 在“礦池”一節中所述的。與其他任一完整節點相同,Jing的節點在比特幣網路中進行接收和傳播未確認交易記錄。然而,Jing的節點也能夠把這些交易記錄打包進入一個新區塊。

​同其他節點一樣,Jing的節點時刻監聽著傳播到比特幣網路的新區塊。而這些新加入的區塊對挖礦節點有著特殊的意 義。礦工間的競爭以新區塊的傳播而結束,如同宣佈誰是最後的贏家。對於礦工們來說,收到一個新區塊進行驗證意味著別人已經贏了,而自己則輸了這場競爭。然而,一輪競爭的結束也代表著下一輪競爭的開始。新區塊並不僅僅是象徵著競賽結束的方格旗;它也是下一個區塊競賽的發令槍。

10.5 打包交易至區塊

驗證交易後,比特幣節點會將這些交易新增到自己的記憶體池中。記憶體池也稱作交易池,用來暫存尚未被加入到區塊的交 易記錄。與其他節點一樣,Jing的節點會收集、驗證並傳遞新的交易。而與其他節點不同的是,Jing的節點會把這些交 易整合到一個候選區塊中。

​讓我們繼續跟進,看下Alice從Bob咖啡店購買咖啡時產生的那個區塊。Alice的交易在區塊 277,316。為了演示本章中提到的概念,我們假設這個區塊是由Jing的挖礦系統挖出的,並且繼續跟進Alice的交易,因 為這個交易已經成為了新區塊的一部分。

Jing的挖礦節點維護了一個區塊鏈的本地副本。當Alice買咖啡 的時候,Jing節點的區塊鏈已經收集到了區塊277,314,並繼續監聽著網路上的交易,在嘗試挖掘新區塊的同時,也監 聽著由其他節點發現的區塊。當Jing的節點在挖礦時,它從比特幣網路收到了區塊277,315。這個區塊的到來標誌著終 結了產出區塊277,315競賽,與此同時也是產出區塊277,316競賽的開始。

在上一個10分鐘內,當Jing的節點正在尋找區塊277,315的解的同時,它也在收集交易記錄為下一個區塊做準備。目前 它已經收到了幾百筆交易記錄,並將它們放進了記憶體池。直到接收並驗證區塊277,315後,Jing的節點會檢查記憶體池中 的全部交易,並移除已經在區塊277,315中出現過的交易記錄,確保任何留在記憶體池中的交易都是未確認的,等待被記 錄到新區塊中。

​Jing的節點立刻構建一個新的空區塊,做為區塊277,316的候選區塊。稱作候選區塊是因為它還沒有包含有效的工作量證明,不是一個有效的區塊,而只有在礦工成功找到一個工作量證明解之後,這個區塊才生效。

​現在,Jing的節點從記憶體池中整合到了全部的交易,新的候選區塊包含有418筆交易,總的礦工費為0.09094925個位元 幣。你可以通過比特幣核心客戶端命令列來檢視這個區塊,如例10-3所示:

例10-3 使用命令列檢索區塊277,316

$ bitcoin-cli get blockhash 277316
0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4

$ bitcoin-cli getblock 0000000000000001b6b9a13b095e96db41c4a928b97ef2d9\44a9b31b2cc7bdc4

{
"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"
}





10.5.1 創幣交易

區塊中的第一筆交易是筆特殊交易,稱為創幣交易或者coinbase交易。這個交易是由Jing的節點構造並用來獎勵礦工們所做的貢獻的。

注意:當塊277,316開採時,每個塊的獎勵是25比特幣。 此後,已經過了一個“減半”時期。 2016年七月份的獎勵為12.5比特幣,2020年達到210000區塊時,將再次減半。

​Jing的節點會建立“向Jing的地址支付25.09094928個比特幣”這樣一個交易,把生成交易的獎勵傳送到自己的錢包。Jing挖出區塊獲得的獎勵金額是coinbase獎勵(25個全新的比特幣)和區塊中全部交易礦工費的總和。

如 例10-4所示:coinbase交易

$ bitcoin-cli getrawtransaction d5ada064c6417ca25c4308bd158c34b77e1c0eca2a73cda16c737e7424afba2f 1

{

"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"
  
  ]
  }
  }
]
}

與常規交易不同,創幣交易沒有輸入,不消耗UTXO。它只包含一個被稱作coinbase的輸入,僅僅用來建立新的位元 幣。創幣交易有一個輸出,支付到這個礦工的比特幣地址。創幣交易的輸出將這25.09094928個比特幣傳送到礦工的比 特幣地址,如本例所示的1MxTkeEP2PmHSMze5tUZ1hAV3YTKu2Gh1N。

10.5.2 Coinbase獎勵與礦工費

為了構造創幣交易,Jing的節點需要計算礦工費的總額,將這418個已新增到區塊交易的輸入和輸出分別進行求和,然 後用輸入總額減去輸出總額得到礦工費總額,公式如下:

Total Fees = Sum(Inputs) - Sum(Outputs)

​在區塊277,316中,礦工費的總額是0.09094925個比特幣。

緊接著,Jing的節點計算出這個新區塊正確的獎勵額。獎勵額的計算是基於區塊高度的,以每個區塊50個比特幣為開始,每產生210,000個區塊減半一次。這個區塊高度是277,316,所以正確的獎勵額是25個比特幣。

​詳細的計算過程可以參看比特幣核心客戶端中的GetBlockValue函式,如例10-5所示:

​例10-5 計算區塊獎勵—函式GetBlockValue, Bitcoin Core Client, main.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) 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;
}

​變數Subsidy表示初始獎勵額,值為 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)總額求和,並返回這個值。

注意: 如果Jing的挖礦節點把coinbase交易寫入區塊,那麼如何防止Jing獎勵自己100甚至1000比特幣? 答案是,不正確的獎勵將被其他人視為無效,浪費了Jing用於工作證明的投入。 只有這個區塊被大家認可,Jing才能得到報酬。

10.5.3創幣交易的結構

經過計算,Jing的節點構造了一個創幣交易,支付給自己25.09094928枚比特幣。

例10-4所示,創幣交易的結構比較特殊,與一般交易輸入需要指定一個先前的UTXO不同,它包含一個“coinbase“輸 入。在之前的章節中,我們已經給出了交易輸入的結構。現在讓我們來比較一下常規交易輸入與創幣交易輸入。表10-1給出了常規交易輸入的結構,表10-2給出的是創幣交易輸入的結構。

​表10-1 常規交易輸入結構

在這裡插入圖片描述

表10-2,coinbase交易輸入結構

在這裡插入圖片描述

在Coinbase交易中,“交易雜湊”欄位32個位元組全部填充0,“交易輸出索引”欄位全部填充0xFF(十進位制的255),這兩個欄位的值表示不引用UTXO。“解鎖指令碼”由coinbase資料代替,資料可以由礦工自定義。

10.5.4 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欄位的起始處。

​我們以例10-4中的區塊277,316為例,coinbase就是交易輸入的“解鎖指令碼“(或scriptSig)欄位,這個欄位的十六進位制值 為03443b0403858402062f503253482f。下面讓我們來解碼這段資料。

​第一個位元組是03,指令碼執行引擎執行這個指令將後面3個位元組壓入指令碼棧,緊接著的3個位元組——0x443b04, 是以小端格式(最低有效位元組在先)編碼的區塊高度。翻轉位元組序得到0x043b44,表示為十進位制是277,316。

​緊接著的幾個十六進位制數(03858402062)用於編碼extra nonce(參見"10.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/以表示支援這個功能。

10-6使用了libbitcoin庫(在之前“其他替代客戶端、資料庫、工具包”中提到)從創世塊中提取coinbase資料,並顯示 出中本聰留下的資訊。libbitcoin庫中自帶了一份創世塊的靜態拷貝,所以這段示例程式碼可以直接取自庫中的創世塊數 據。

​例10-6 從創世區塊中提取coinbase資料​

code/satoshi-words.cpp[]

​例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

10.6 構造區塊頭

為了構造區塊頭,挖礦節點需要填充六個欄位,如表10-3中所示。

表10-3 區塊頭結構

在這裡插入圖片描述

​在區塊277,316被挖出的時候,區塊結構中用來表示版本號的欄位值為2,長度為4位元組,以小端格式編碼值為 0x20000000。

​接著,挖礦節點需要填充“前區塊雜湊”,在本例中,這個值為Jing的節點從網路上接收到的區塊277,315 的區塊頭雜湊值,它是區塊277316候選區塊的父區塊。區塊277,315的區塊頭雜湊值為:

0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569

提示:通過選擇候選塊頭中的先前塊雜湊欄位指定的特定父區塊,Jing正在通過確認挖礦能力來擴充套件區塊鏈。 從本質上講,這是用他的採礦權為最長難度的有效鏈進行的“投票”。

​為了向區塊頭填充merkle根欄位,要將全部的交易組成一個merkle樹。創幣交易作為區塊中的首個交易,後將餘下的 418筆交易添至其後,這樣區塊中的交易一共有419筆。在之前,我們已經見到過“Merkle樹”,樹中必須有偶數個葉子 節點,所以需要複製最後一個交易作為第420個葉子節點,每個葉子節點是對應交易的雜湊值。這些交易的雜湊值逐層地、成對地組合,直到最終組合併成一個根節點。merkle數的根節點將全部交易資料摘要為一個32位元組長度的值,例10-3中 merkel根的值如下:

c91c008c26e50763e9f548bb8b2fc323735f73577effbc55502c51eb4cc7cf2e

​挖礦節點會繼續新增一個4位元組的時間戳,以Unix紀元時間編碼,即自1970年1月1日0點到當下總共流逝的秒數。本例 中的1388185914對應的時間是2013年12月27日,星期五,UTC/GMT。

接下來,Jing的節點需要填充Target欄位(難度目標值),為了使得該區塊有效,這個欄位定義了所需滿足的工作量證明的難度。難度在區塊中以“尾數-指數”的格式,編碼並儲存,這種格式稱作target bits(難度位)。這種編碼的首位元組表示指數,後面的3位元組表示尾數(系 數)。以區塊277316為例,難度位的值為0x1903a30c,0x19是指數的十六進位制格式,後半部0x03a30c是係數。這部分的概念在後面的“難度目標與難度調整”和“難度表示”有詳細的解釋。

​最後一個欄位是nonce,初始值為0。

​區塊頭完成全部的欄位填充後,挖礦就可以開始進行了。挖礦的目標是找到一個使區塊頭雜湊值小於難度目標的 nonce。挖礦節點通常需要嘗試數十億甚至數萬億個不同的nonce取值,直到找到一個滿足條件的nonce值。

10.7 構建區塊

既然Jing的節點已經構建了一個候選區塊,那麼就輪到Jing的礦機對這個新區塊進行“挖掘”,求解工作量證明演算法以使這個區塊有效。從本書中我們已經學習了比特幣系統中不同地方用到的雜湊加密函式。比特幣挖礦過程使用的是 SHA256雜湊函式。

​用最簡單的術語來說,挖礦就是重複計算區塊頭的雜湊值,不斷修改該引數,直到與雜湊值匹配的一個過程。雜湊函式 的結果無法提前得知,也沒有能得到一個特定雜湊值的模式。雜湊函式的這個特性意味著:得到雜湊值的唯一方法是不斷的嘗試,每次隨機修改輸入,直到出現適當的雜湊值。

10.7.1 工作量證明演算法

​雜湊函式輸入一個任意長度的資料,輸出一個長度固定且絕不雷同的值,可將其視為輸入的數字指紋。對於特定輸入,雜湊的結果每次都一樣,任何人都可以用相同的雜湊函式,計算和驗證雜湊結果。一個加密雜湊函式的主要特徵就是不同的 輸入幾乎不可能出現相同的數字指紋。因此,有意的選擇一個輸入去生成一個想要的雜湊值值是幾乎不可能的,更別提用隨機的方式生成想要的雜湊值了。

​無論輸入的大小是多少,SHA256函式的輸出的長度總是256bit。在例10-8中,我們將使用Python直譯器來計算語句 “I am Satoshi Nakamoto” 的SHA256的雜湊值。

​例10-8 SHA256示例

$ python
Python 2.7.1

>
>
>
 import hashlib

>
>
>
 print hashlib.sha256("I am Satoshi Nakamoto").hexdigest()
5d7c7ba21cbbcd75d14800b100252d5b428e5b1213d27c385bc141ca6b47989e

在例10-8中, 5d7c7ba21cbbcd75d14800b100252d5b428e5b1213d27c385bc141ca6b47989e 是"I am Satoshi Nakamoto"的雜湊值。改變原句中的任何一個字母、標點、或增加字母都會產生不同的雜湊值。

如果我們改變原句,得到的應該是完全不同的雜湊值。 例如,我們在句子末尾加上一個數字,執行例10-9中的Python指令碼。例10-9 通過反覆修改 nonce 來生成不同雜湊值的指令碼(SHA256)

code/hash_example.py[]

執行這個指令碼就能生成這些只是末尾數字不同的語句的雜湊值。例10-10 中顯示了我們只是增加了這個數字,卻得到了非 常不同的雜湊值。

例10-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開頭。幸運的是,這很容易!在例10-10中語句 “I am Satoshi Nakamoto13” 的雜湊值是 0ebc56d59a34f5082aaef3d66b37a661696c2b618e62432727216ba9531041a5 ,剛好滿足條件。我們得到它用了13次。用概率的角度 來看,如果雜湊函式的輸出是平均分佈的,我們可以期望每16次得到一個以0開頭的雜湊值(十六進位制個位數字為0到 F)。從數字的角度來看,我們要找的是小於 0x1000000000000000000000000000000000000000000000000000000000000000 的雜湊值。

我們稱這個為Target目標閾值,我們的目的是找到一個小於這個目標的雜湊值。如果我們減小這個目標值,那找到一個小於它的雜湊值會越來越難。

簡單打個比方,想象人們不斷扔一對骰子以得到小於一個特定點數的遊戲。第一局,目標是12。只要你不扔出兩個6, 你就會贏。然後下一局目標為11。玩家只能扔10或更小的點數才能贏,不過也很簡單。假如幾局之後目標降低為了5。

現在有一半機率以上扔出來的骰子加起來點數會超過5,因此無效。隨著目標越來越小,要想贏的話,扔骰子的次數會 指數級的上升。最終當目標為2時(最小可能點數),只有一個人平均扔36次或2%扔的次數中,他才能贏。從一個知道骰子游戲目標為2的觀察者的角度來看,如果有人要成功中獎,假設他平均嘗試了36次。

換句話說,可以估計從實現目標難度取得成功所需的工作量。 當演算法是基於諸如SHA256的確定性函式時,輸入本身就成為證據,必須要一定的工作量才能產生低於目標的結果。 因此,稱之為工作量證明。

提示:儘管每次嘗試產生一個隨機的結果,但是任何可能的結果的概率可以預先計算。 因此,指定特定難度的結果構成了具體的工作量證明。

​在例10-10中,成功的nonce為13,且這個結果能被所有人獨立確認。任何人將13加到語句 “I am Satoshi Nakamoto” 後面再計算雜湊值都能確認它比目標值要小。這個正確的結果同時也是工作量證明(Proof of Work),因為它證明我們的確花時間找到了這個nonce。驗證這個雜湊值只需要一次計算,而我們找到它卻花了13次。如果目標值更小(難度更大),那我們需要多得多的雜湊計算才能找到合適的nonce,但其他人驗證它時只需要一次雜湊計算。此外,知道目標值後,任何人都可以用統計學來估算其難度,因此就能知道找到這個nonce需要多少工作。

提示:工作證明必須產生小於目標的雜湊值。 更高的目標意味著找到低於目標的雜湊是不太困難的。 較低的目標意味著在目標下方找到雜湊更難。 目標和難度是成反比。

​比特幣的工作量證明和例10-10中的挑戰非常類似。礦工用一些交易構建一個候選區塊。接下來,這個礦工計算這個區塊頭資訊的雜湊值,看其是否小於當前目標值。如果這個雜湊值不小於目標值,礦工就會修改這個nonce(通常將之加 1)然後再試一次。按當前比特幣系統的難度,礦工得試10^15次(10的15次方)才能找到一個合適的nonce使區塊頭資訊雜湊值足夠小。

​ 例10-11是一個簡化很多的工作量證明演算法的實現。

​ 例10-11 簡化的工作量證明演算法

code/proof-of-work-example.py\[]

你可以任意調整難度值(按二進位制bit數來設定,即雜湊值開頭多少個bit必須是0)。然後執行程式碼,看看在你的計算機 上求解需要多久。在例10-12中,你可以看到該程式在一個普通膝上型電腦上的執行情況。

例10-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,你就把雜湊查詢空間縮減了一半。在例10-12中,為尋找一個nonce使得雜湊值開頭的26位值為0,一共嘗試了8千多萬次。即使家用筆記本每秒可以達270,000多次雜湊計算,這個查詢依然需要10分鐘。

​在寫這本書的時候,比特幣網路要尋找區塊頭資訊雜湊值小於

0000000000000000029AB9000000000000000000000000000000000000000000

​可以看出,這個目標雜湊值開頭的0多了很多。這意味 著可接受的雜湊值範圍大幅縮減,因而找到正確的雜湊值更加困難。生成下一個區塊需要網路每秒計算1.8 septa-hashes((thousand billion billion次雜湊)。這看起來像是不可能的任務,但幸運的是比特幣網路已經擁有3EH/sec的處理能力,平均每10分鐘就可以找到一個新區塊。

10.7.2 難度表示

​在例10-3中,我們在區塊中看到難度目標,其被標為"難度位"或簡稱"bits"。在區塊277,316中,它的值為 0x1903a30c。 這個標記的值被存為係數/指數格式,前兩位十六進位制數字為冪(exponent),接下來得六位為係數(coefficient)。在這個區塊裡,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天,才能為某一個區塊找到正確的雜湊值。

10.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\)

​ 例10-13展示了比特幣核心客戶端中的難度調整程式碼。

例10-13 工作量證明的難度調整 原始檔**( pow.cpp檔案鐘的CalculateNextWorkRequired() 函式)**

第43行函式 GetNextWorkRequired()// Limit adjustment step

// Limit adjustment step
    int64_t nActualTimespan = pindexLast-
>
GetBlockTime() - nFirstBlockTime;
    LogPrintf("  nActualTimespan = %d  before bounds\n", nActualTimespan);
    if (nActualTimespan 
<
 params.nPowTargetTimespan/4)
        nActualTimespan = params.nPowTargetTimespan/4;
    if (nActualTimespan 
>
 params.nPowTargetTimespan*4)
        nActualTimespan = params.nPowTargetTimespan*4;

    // Retarget
    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    arith_uint256 bnNew;
    arith_uint256 bnOld;
    bnNew.SetCompact(pindexLast-
>
nBits);
    bnOld = bnNew;
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;

    if (bnNew 
>
 bnPowLimit)
        bnNew = bnPowLimit;

注意:雖然目標校準每2,016個塊發生,但是由於Bitcoin Core客戶端的一個錯誤,它是基於之前的2,015個塊的總時間(不應該是2,016個),導致重定向偏差向較高難度提高0.05%。

​引數Interval(2,016區塊)和TargetTimespan(1,209,600秒即兩週)的定義在檔案chainparams.cpp中。

​為了防止難度的變化過快,每個週期的調整幅度必須小於一個因子(值為4)。如果要調整的幅度大於4倍,則按4倍調 整。由於在下一個2,016區塊的週期不平衡的情況會繼續存在,所以進一步的難度調整會在下一週期進行。因此平衡哈 希計算能力和難度的巨大差異有可能需要花費幾個2,016區塊週期才會完成。

提示:尋找一個比特幣區塊需要整個網路花費10分鐘來處理,每發現2,016個區塊時會根據前2,016個區塊完成的時間對難度進行調整。

​值得注意的是目標難度與交易的數量和金額無關。這意味著雜湊算力的強弱,即讓比特幣更安全的電力投入量,與交易 的數量完全無關。換句話說,當比特幣的規模變得更大,使用它的人數更多時,即使雜湊算力保持當前的水平,比特幣 的安全性也不會受到影響。雜湊算力的增加表明更多的人為得到比特幣回報而加入了挖礦隊伍。只要為了回報,公平正 當地從事挖礦的礦工群體保持足夠的雜湊算力,"接管"攻擊就不會得逞,讓比特幣的安全無虞。

​目標難度和挖礦電力消耗與將比特幣兌換成現金以支付這些電力之間的關係密切相關。高效能挖礦系統就是要用當前矽 晶片以最高效的方式將電力轉化為雜湊算力。挖礦市場的關鍵因素就是每度電轉換為比特幣後的價格。因為這決定著挖 礦活動的營利性,也因此刺激著人們選擇進入或退出挖礦市場。

10.8 成功構建區塊

前面已經看到,Jing的節點建立了一個候選區塊,準備拿它來挖礦。Jing有幾個安裝了ASIC(專用積體電路)的礦機, 上面有成千上萬個積體電路可以超高速地並行執行SHA256演算法。這些定製的硬體通過USB連線到他的挖礦節點上。接 下來,執行在Jing的桌面電腦上的挖礦節點將區塊頭資訊傳送給這些硬體,讓它們以每秒億萬次的速度進行nonce測 試。

​在對區塊277,316的挖礦工作開始大概11分鐘後,這些硬體裡的其中一個求得了解併發回挖礦節點。當把這個結果放進 區塊頭時,nonce 4,215,469,401 就會產生一個區塊雜湊值:

0000000000000002a7bbd25a417c0374cc55261021e8a9ca74442b01284f0569

而這個值小於難度目標值:

0000000000000003A30C00000000000000000000000000000000000000000000

​Jing的挖礦節點立刻將這個區塊發給它的所有相鄰節點。這些節點在接收並驗證這個新區塊後,也會繼續傳播此區塊。當這個新區塊在網路中擴散時,每個節點都會將它作為區塊277,316加到自身節點的區塊鏈副本中。當挖礦節點收到並驗證了這個新區塊後,它們會放棄之前對構建這個相 同高度區塊的計算,並立即開始計算區塊鏈中下一個區塊的工作。

​下節將介紹節點進行區塊驗證、最長鏈選擇、達成共識,並以此形成一個去中心化區塊鏈的過程。

10.9 校驗新區塊

​比特幣共識機制的第三步是通過網路中的每個節點獨立校驗每個新區塊。當新區塊在網路中傳播時,每一個節點在將它轉發到其節點之前,會進行一系列的測試去驗證它。這確保了只有有效的區塊會在網路中傳播。獨立校驗還確保了誠實 的礦工生成的區塊可以被納入到區塊鏈中,從而獲得獎勵。行為不誠實的礦工所產生的區塊將被拒絕,這不但使他們失 去了獎勵,而且也浪費了本來可以去尋找工作量證明解的機會,因而導致其電費虧損。

​當一個節點接收到一個新的區塊,它將對照一個長長的標準清單對該區塊進行驗證,若沒有通過驗證,這個區塊將被拒絕。這些標準可以在比特幣核心客戶端的CheckBlock函式和CheckBlockHead函式中獲得,

它包括:

▷ 區塊的資料結構語法上有效

▷ 區塊頭的雜湊值小於目標難度(確認包含足夠的工作量證明)

▷ 區塊時間戳早於驗證時刻未來兩個小時(允許時間錯誤)

▷ 區塊大小在長度限制之內

▷ 第一個交易(且只有第一個)是coinbase交易

▷ 使用檢查清單驗證區塊內的交易並確保它們的有效性

參見之前章節 “交易的獨立校驗”一節已經討論過這個清單。每一個節點對每一個新區塊的獨立校驗,確保了礦工無法欺詐。在前面的章節中,我們看到了礦工們如何去記錄一筆交 易,以獲得在此區塊中創造的新比特幣和交易費。為什麼礦工不為他們自己記錄一筆交易去獲得數以千計的比特幣?這是因為每一個節點根據相同的規則對區塊進行校驗。一個無效的coinbase交易將使整個區塊無效,這將導致該區塊被拒 絕,因此,該交易就不會成為總賬的一部分。礦工們必須構建一個完美的區塊,基於所有節點共享的規則,並且根據正 確工作量證明的解決方案進行挖礦,他們要花費大量的電力挖礦才能做到這一點。如果他們作弊,所有的電力和努力都 會浪費。這就是為什麼獨立校驗是去中心化共識的重要組成部分。

10.10 區塊鏈的組裝與選擇

​比特幣去中心化的共識機制的最後一步是將區塊集合至有最大工作量證明的鏈中。一旦一個節點驗證了一個新的區塊, 它將嘗試將新的區塊連線到到現存的區塊鏈,將它們組裝起來。

節點維護三種區塊:第一種是連線到主鏈上的,第二種是從主鏈上產生分支的(備用鏈),最後一種是在已知鏈中沒有 找到已知父區塊的。在驗證過程中,一旦發現有不符合標準的地方,驗證就會失敗,這樣區塊會被節點拒絕,所以也不 會加入到任何一條鏈中。

​任何時候,主鏈都是累計了最多難度的區塊鏈。在一般情況下,主鏈也是包含最多區塊的那個鏈,除非有兩個等長的鏈 並且其中一個有更多的工作量證明。主鏈也會有一些分支,這些分支中的區塊與主鏈上的區塊互為“兄弟”區塊。這些區 塊是有效的,但不是主鏈的一部分。 保留這些分支的目的是如果在未來的某個時刻它們中的一個延長了並在難度值上超 過了主鏈,那麼後續的區塊就會引用它們。在“10.10.1 區塊鏈分叉”,我們將會看到在同樣的區塊高度,幾乎同時挖出區 塊時,候選鏈是如何產生的。

​當節點接收到新區塊,它會嘗試將這個區塊插入到現有區塊鏈中。節點會看一下這個區塊的“previous block hash”字 段,這個欄位是該區塊對其父區塊的引用。同時,新的節點將嘗試在已存在的區塊鏈中找出這個父區塊。大多數情況 下,父區塊是主塊鏈的“頂點”,這就意味著這個新的區塊延長了主鏈。舉個例子,一個新的區塊——區塊277,316引用 了它的父區塊——區塊277,315。收到277316區塊的大部分節點都已經將277315最為主鏈的頂端,因此,連線這個新區塊並延長區塊鏈。

​有時候,新區塊所延長的區塊鏈並不是主鏈,這一點我們將在“10.10.1 區塊鏈分叉”中看到。在這種情況下,節點將新的區塊新增到備用鏈,同時比較備用鏈與主鏈的難度。如果備用鏈比主鏈積累了更多的難度,節點將收斂於備用鏈,意味 著節點將選擇備用鏈作為其新的主鏈,而之前那個老的主鏈則成為了備用鏈。如果節點是一個礦工,它將開始構造新的 區塊,來延長這個更新更長的區塊鏈。

​如果節點收到了一個有效的區塊,而在現有的區塊鏈中卻未找到它的父區塊,那麼這個區塊被認為是“孤塊”。孤塊會被 儲存在孤塊池中,直到它們的父區塊被節點收到。一旦收到了父區塊並且將其連線到現有區塊鏈上,節點就會將孤塊從 孤塊池中取出,並且連線到它的父區塊,讓它作為區塊鏈的一部分。當兩個區塊在很短的時間間隔內被挖出來,節點有 可能會以相反的順序接收到它們,這個時候孤塊現象就會出現。

選擇了最大難度的區塊鏈後,所有的節點最終在全網範圍內達成共識。隨著更多的工作量證明被新增到鏈中,鏈的暫時 性差異最終會得到解決。挖礦節點通過“投票”來選擇它們想要延長的區塊鏈,當它們挖出一個新塊並且延長了一個鏈, 新塊本身就代表它們的投票。

​相互競爭的鏈之間是存在差異的,下節我們將看到節點是怎樣通過獨立選擇最長難度鏈來解決這種差異的。

10.10.1 區塊鏈分叉

因為區塊鏈是去中心化的資料結構,所以不同副本之間不能總是保持一致。區塊有可能在不同時間到達不同節點,導致節點有不同的區塊鏈全貌。解決的辦法是,每一個節點總是選擇並嘗試延長代表累計了最大工作量證明的區塊鏈,也就 是最長的或最大累計工作的鏈(greatest cumulative work chain)。節點通過累加鏈上的每個區塊的工作量,得到建立這個鏈所要付出的工作量證明的總量。只要所有的節點選擇最長累計工作的區塊鏈,整個比特幣網路最終會收斂到一致的狀態。分叉即在不同區塊鏈間發生的臨時差異,當更多的區塊新增到了某個分叉中,這個問題便會迎刃而解。

提示由於全球網路中的傳輸延遲,本節中描述的區塊鏈分叉自動會發生。 我們也將在本章稍後再看看故意引起的分叉。

在接下來的幾個圖表中,我們將通過網路跟蹤“fork”事件的進展。 該圖是比特幣網路的簡化表示。 為了便於描述,不同的區塊被顯示為不同的形狀(星形,三角形,倒置三角形,菱形),遍佈網路。 網路中的每個圓表示一個節點。

每個節點都有自己的全域性區塊鏈檢視。 當每個節點從其鄰居接收區塊時,它會更新其自己的區塊鏈副本,選擇最大累積工作鏈。 為便於描述,每個節點包含一個圖形形狀,表示它相信的區塊處於主鏈的頂端。 因此,如果在節點裡面看到星形,那就意味著該節點認為星形區塊處於主鏈的頂端。

在第一張圖(圖10-2)中,網路有一個統一的區塊鏈視角,以星形區塊為主鏈的“頂點”。

在這裡插入圖片描述

​ 圖10-2

​當有兩個候選區塊同時想要延長最長區塊鏈時,分叉事件就會發生。正常情況下,分叉發生在兩名礦工在較短的時間 內,各自都算得了工作量證明解的時候。兩個礦工在各自的候選區塊一發現解,便立即傳播自己的“獲勝”區塊到網路 中,先是傳播給鄰近的節點而後傳播到整個網路。每個收到有效區塊的節點都會將其併入並延長區塊鏈。如果該節點在 隨後又收到了另一個候選區塊,而這個區塊又擁有同樣父區塊,那麼節點會將這個區塊連線到候選鏈上。其結果是,一 些節點收到了一個候選區塊,而另一些節點收到了另一個候選區塊,這時兩個不同版本的區塊鏈就出現了。在圖10-3中,我們看到兩個礦工(NodeX和NodeY)幾乎同時挖到了兩個不同的區塊。這兩個區塊是頂點區塊——星形區塊的子區塊,可以 延長這個區塊鏈。為了方便檢視,我們把節點X產生的區塊標記為三角形,把節點Y生產的區塊標記為倒三角形。

在這裡插入圖片描述

​ 圖10-3

​例如,我們假設礦工節點X找到擴充套件區塊鏈工作量證明的解,即三角形區塊,構建在星形父區塊的頂端。與此同時,同樣進行星形區塊擴充套件的節點Y也找到了擴充套件區塊鏈工作量證明的解,即倒三角形區塊作為候選區塊。現在有兩個可能的塊,節點X的三角形區塊和節點Y的倒三角形區塊,這兩個區塊都是有效的,均包含有效的工作量證明解並延長同一 個父區塊。這個兩個區塊可能包含了幾乎相同的交易,只是在交易的排序上有些許不同。

​當兩個區塊開始在網路傳播時,一些節點首先接收到三角形區塊,另外一些節點首先接收倒三角形區塊。如下圖10-4所示,比特幣網路上的節點對於區塊鏈的頂點產生了分歧,一派以三角形區塊為頂點,而另一派以倒三角形區塊為頂點。

在這裡插入圖片描述
​ 圖10-4

​在圖中,假設節點X首先接收到三角形塊,並用它擴充套件星形鏈。節點X選擇三角形區塊為主鏈。之後,節點X也收到倒三角區塊。由於是第二次收到,因此它判定這個倒三角形區塊是競爭失敗的產物,認為是無效區塊。然而,倒三角形的區塊不會被丟棄。它被連結到星形鏈的父區塊,並形成備用鏈。雖然節點X認為自己已經正確選擇了獲勝鏈,但是它還會儲存“丟失”鏈,使得“丟失”鏈如果可能最終“獲勝”,它還具有重新打包的所需的資訊。

​在網路的另一端,節點Y根據自己的視角構建一個區塊鏈。首先獲得倒三角形區塊,並選擇這條鏈作為“贏家”。當它稍後收到三角形區塊時,它也將三角形區塊連線到星形鏈的父區塊作為備用鏈。

​雙方都是“正確的”或“不正確的”。兩者都是自己關於區塊鏈的有效立場。只有事後,才能理解這兩個競爭鏈如何通過額外的工作得到延伸。

節點X陣營的其他節點將立即開始挖掘候選區塊,以“三角形”作為擴充套件區塊鏈的頂端。通過將三角形作為候選區塊的父區塊,它們用自己的雜湊算力進行投票。它們的投票標明支援自己選擇的鏈為主鏈。

​同樣,節點Y陣營的其他節點,將開始構建一個以倒三角形作為其父節點的候選節點,擴充套件它們認為是主鏈的鏈。比賽再次開始。分叉問題幾乎總是在一個區塊內就被解決了。網路中的一部分算力專注於“三角形”區塊為父區塊,在其之上建立新的區 塊;另一部分算力則專注在“倒三角形”區塊上。即便算力在這兩個陣營中平均分配,也總有一個陣營搶在另一個陣營前發現工作量證明解並將其傳播出去。在這個例子中我們可以打個比方,假如工作在“三角形”區塊上的礦工找到了一個“菱形”區塊 延長了區塊鏈(星形-三角形-菱形),他們會立刻傳播這個新區塊,整個網路會都會認為這個區塊是有效的,如下圖10-5所示。

在這裡插入圖片描述

​ 圖10-5

​選擇“三角形”作為上一輪中勝出者的所有節點將簡單地將區塊鏈擴充套件一個塊。然而,選擇“倒三角”的節點現在將看到兩個鏈:星形-三角形-菱形和星型-到三角形。星形-三角形-菱形這條鏈現在比其他鏈條更長(更多累積的工作)。因此,這些節點將星形-三角形-菱形設定為主鏈,並將星型-倒三角形鏈變為備用鏈,如圖10-6所示。這是一個鏈的重新共識,因為這些節點被迫修改他們對塊鏈的立場,把自己納入更長的鏈。任何從事延伸星形-倒三角形的礦工現在都將停止這項工作,因為他們的候選人是“孤兒”,因為他們的父母“倒三角形”不再是最長的連鎖。 “倒三角形”內的交易重新插入到記憶體池中用來包含在下一個塊中,因為它們所在的塊不再位於主鏈中。整個網路重新回到單一鏈狀態,星形-三角形-菱形,“菱形”成為鏈中的最後一個塊。所有礦工立即開始研究以“菱形”為父區塊的候選塊,以擴充套件這條星形-三角形-菱形鏈。

在這裡插入圖片描述

​ 圖10-6

從理論上來說,兩個區塊的分叉是有可能的,這種情況發生在因先前分叉而相互對立起來的礦工,又幾乎同時發現了兩 個不同區塊的解。然而,這種情況發生的機率是很低的。單區塊分叉每週都會發生,而雙塊分叉則非常罕見。比特幣將區塊間隔設計為10分鐘,是在更快速的交易確認和更低的分叉概率間作出的妥協。更短的區塊產生間隔會讓交 易清算更快地完成,也會導致更加頻繁地區塊鏈分叉。與之相對地,更長的間隔會減少分叉數量,卻會導致更長的清算時間。

10.11 挖礦和算力競賽

比特幣挖礦是一個極富競爭性的行業。自從比特幣存在開始,每年比特幣算力都成指數增長。一些年份的增長還體現出技術的變革,比如在2010年和2011年,很多礦工開始從使用CPU升級到使用GPU,進而使用FGPA(現場可程式設計門陣 列)挖礦。在2013年,ASIC挖礦的引入,把SHA256演算法直接固化在挖礦專用的矽晶片上,引起了算力的另一次巨大飛 躍。一臺採用這種晶片的礦機可以提供的算力,比2010年比特幣網路的整體算力還要大。

下表表示了比特幣網路開始執行後最初五年的總算力:

2009

0.5 MH/sec–8 MH/sec \(16× growth\)

2010

8 MH/sec–116 GH/sec \(14,500× growth\)

2011

16 GH/sec–9 TH/sec \(562× growth\)

2012

9 TH/sec–23 TH/sec \(2.5× growth\)

2013

23 TH/sec–10 PH/sec \(450× growth\)

2014

10 PH/sec–300 PH/sec \(3000× growth\)

2015

300 PH/sec-800 PH/sec \(266× growth\)

2016

800 PH/sec-2.5 EH/sec \(312× growth\)\)

在圖10-7的圖表中,我們可以看到近兩年裡,礦業和比特幣的成長引起了比特幣網路算力的指數增長(每秒網路總算 力)。

在這裡插入圖片描述

隨著比特幣挖礦算力的爆炸性增長,與之匹配的難度也相應增長。圖10-8中的相對難度值顯示了當前難度與最小難度 (第一個塊的難度)的比例。

在這裡插入圖片描述

近兩年,ASIC晶片變得更加密集,特徵尺寸接近晶片製造業前沿的16奈米。挖礦的利潤率驅動這個行業以比通用計算 更快的速度發展。

目前,ASIC製造商的目標是超越通用CPU晶片製造商,設計特徵尺寸為14奈米的晶片。對比特幣挖礦而言,已經沒有更多飛躍的空間,因為這個行業已經觸及了摩爾定律的最前沿。摩爾定律指出計算能力每18個月增加一倍。

儘管如此,隨著更高密度的晶片和資料中心的部署競賽,網路算力繼續保持同步的指數增長。現在的競爭已經不再是比較單一晶片的能力,而是一個礦場能塞進多少晶片,並處理好散熱和供電問題。

10.11.1 隨機值升位方案 the extra nonce solution

2012年以來,比特幣挖礦發展出一個解決區塊頭基本結構限制的方案。在比特幣的早期,礦工可以通過遍歷隨機數(Nonce)獲得符合要求的hash來挖出一個塊。

難度增長後,礦工經常在嘗試了40億個值後仍然沒有出塊。然而,這很容 易通過讀取塊的時間戳並計算經過的時間來解決。因為時間戳是區塊頭的一部分,它的變化可以讓礦工用不同的隨機值 再次遍歷。當挖礦硬體的速度達到了4GH/秒,這種方法變得越來越困難,因為隨機數的取值在一秒內就被用盡了。

當出現ASIC礦機並很快達到了TH/秒的hash速率後,挖礦軟體為了找到有效的塊,需要更多的空間來儲存nonce值。可以把 時間戳延後一點,但將來如果把它移動得太遠,會導致區塊變為無效。

區塊頭需要資訊來源的一個新的“變革”。解決方案是使用coinbase交易作為額外的隨機值來源,因為coinbase指令碼可以儲存2-100位元組的資料,礦工們開始使用這個空間作為額外隨機值的來源,允許他們去探索一個大得多的區塊頭值範圍來找到有效的塊。這個coinbase交易包含在merkle樹中,這意味著任何coinbase指令碼的變化將導致Merkle根的變化。

8個位元組的額外隨機數,加上4個位元組的“標準”隨機數,允許礦工每秒嘗試2^96(8後面跟28個零)種可能性而無需修改時間戳。如果未來礦工穿過了以上所有的可能性,他們還可以通過修改時間戳來解決。同樣,coinbase指令碼中也有更多額外的空間可以為將來隨機數的擴充套件做準備。

10.11.2 礦池

在這個激烈競爭的環境中,個體礦工獨立工作(也就是solo挖礦)沒有一點機會。他們找到一個區塊以抵消電力和硬體 成本的可能性非常小,以至於可以稱得上是賭博,就像是買彩票。就算是最快的消費型ASIC也不能和那些在巨大機房裡擁有數萬晶片並靠近水電站的商業礦場競爭。

現在礦工們合作組成礦池,彙集數以千計參與者們的算力並分享獎勵。通 過參加礦池,礦工們得到整體回報的一小部分,但通常每天都能得到,因而減少了不確定性。

讓我們來看一個具體的例子。假設一名礦工已經購買了算力共計14,000GH/S,或14TH/S的裝置,在2017年,它的價 值大約是2500美元。

該裝置執行功率為1.3千瓦(KW),每日耗電32度,每日平均成本1或2美元。以目前的比特幣難度, 該礦工solo方式挖出一個塊平均需要4年。如果這個礦工確實在這個時限內挖出一個區塊,獎勵12.5個比特幣,如果 每個比特幣價格約為1000美元(譯者:這是作者出版此書的價格,2017年10月22日譯者看到的價格是5880美元),可以得到12,500美元的收入。這甚至不能覆蓋整個硬體和整個時間段的電力消耗,淨虧損約為1,000美元。而且,在4年的時間週期內能否挖出一個塊還主要靠礦工的運氣。他有可能在4年中得到兩個塊從而賺到非常大的利潤。或者,他可能5年都找不到一個塊,從而遭受經濟損失。

更糟的是,比特幣的工作證明 (POW)演算法的難度可能在這段時間內顯著上升,按照目前算力增長的速度,這意味著礦工在裝置被下一代更有效率的礦機取代之前,最多有1年的時間取得成果。如果這個礦工加入礦池,而不是等待4年內可能出現一次的暴利,他每週能賺取大約50-60美元。礦池的常規收入能幫他隨時間攤銷硬體和電力的成本,並且不用承擔巨大的風險。在1-2年後,硬體仍然會過時,風險仍然很高,但在此期間的收入至少是定期的和可靠的。

從財務資料分析,這隻有非常低的電力成本(每千瓦不到1美分),非常大的規模時才有意義。礦池通過專用挖礦協議協調成百上千的礦工。

個人礦工在建立礦池賬號後,設定他們的礦機連線到礦池伺服器。他們的 挖礦裝置在挖礦時保持和礦池伺服器的連線,和其他礦工同步各自的工作。這樣,礦池中的礦工分享挖礦任務,之後分 享獎勵。成功出塊的獎勵支付到礦池的比特幣地址,而不是單個礦工的。一旦獎勵達到一個特定的閾值,礦池伺服器便會定期支 付獎勵到礦工的比特幣地址。

通常情況下,礦池伺服器會為提供礦池服務收取一個百分比的費用。參加礦池的礦工把搜尋候選區塊的工作量分割,並根據他們挖礦的貢獻賺取“份額”。礦池為賺取“份額”設定了一個低難度 的目標,通常比比特幣網路難度低1000倍以上。

當礦池中有人成功挖出一塊,礦池獲得獎勵,並和所有礦工按照他們做 出貢獻的“份額”數的比例分配。礦池對任何礦工開放,無論大小、專業或業餘。一個礦池的參與者中,有人只有一臺小礦機,而有些人有一車庫高階挖 礦硬體。有人只用幾十度電挖礦,也有人會用一個資料中心消耗兆瓦級的電量。礦池如何衡量每個人的貢獻,既能公平 分配獎勵,又避免作弊的可能?答案是在設定一個較低難度的前提下,使用比特幣的工作量證明演算法來衡量每個礦工的 貢獻。

因此,即使是池中最小的礦工也經常能分得獎勵,這足以激勵他們為礦池做出貢獻。通過設定一個較低的取得份 額的難度,礦池可以計量出每個礦工完成的工作量。每當礦工發現一個小於礦池難度的區塊頭hash,就證明了它已經完 成了尋找結果所需的hash計算。更重要的是,這些為取得份額貢獻而做的工作,能以一個統計學上可衡量的方法,整體 尋找一個比特幣網路的目標雜湊值。成千上萬的礦工嘗試較小區間的hash值,最終可以找到符合比特幣網路要求的結 果。

讓我們回到骰子游戲的比喻。如果骰子玩家的目標是扔骰子結果都小於4(整體網路難度),一個礦池可以設定一個更 容易的目標,統計有多少次池中的玩家扔出的結果小於8。當池中的玩家扔出的結果小於8(礦池份額目標),他們得到份額,但他們沒有贏得遊戲,因為沒有完成遊戲目標(小於4)。但池中的玩家會更經常的達到較容易的礦池份額目 標,規律地賺取他們的份額,儘管他們沒有完成更難的贏得比賽的目標。時不時地,池中的一個成員有可能會扔出一個小於4的結果,礦池獲勝。然後,收益可以在池中玩家獲得的份額基礎上分配。

儘管目標設定為8或更少並沒有贏得遊戲,但是這是一個衡量玩家們扔出的點數的公平方法,同時它偶爾會產生一個小於4的結果。同樣的,一個礦池會將礦池難度設定在保證一個單獨的礦工能夠頻繁地找到一個符合礦池難度的區塊頭hash來贏取份 額。時不時的,某次嘗試會產生一個符合比特幣網路目標的區塊頭hash,產生一個有效塊,然後整個礦池獲勝。

10.11.2.1 託管礦池

大部分礦池是“託管的”,意思是有一個公司或者個人經營一個礦池伺服器。礦池伺服器的所有者叫礦池管理員,同時他 從礦工的收入中收取一個百分比的費用。礦池伺服器執行專業軟體以及協調池中礦工們活動的礦池採礦協議。礦池伺服器同時也連線到一個或更多比特幣完全節 點並直接訪問一個塊鏈資料庫的完整副本。這使得礦池伺服器可以代替礦池中的礦工驗證區塊和交易,緩解他們執行一 個完整節點的負擔。

對於池中的礦工,這是一個重要的考量,因為一個完整節點要求一個擁有最少100-150GB的永久儲存空間(磁碟)和最少2GB到4GB記憶體(RAM)的專用計算機。

此外,執行一個完整節點的比特幣軟體需要監控、維護和頻繁升級。由於缺乏維護或資源導致的任何當機都會傷害到礦工的利潤。對於很多礦工來說,不需要跑一個完整節點就能採礦,也是加入托管礦池的一大好處。

礦工連線到礦池伺服器使用一個採礦協議比如Stratum(STM)或者 GetBlockTemplate(GBT)。一箇舊標準GetWork(GWK)自從2012年底已經基本上過時了,因為它不支援在hash速度超過4GH/S時採礦。STM和GBT協議都建立包含候選區塊頭模板的區塊模板。礦池伺服器通過打包交易,新增coinbase交易(和額外的隨機值空間),計算MERKLE根, 並連線到上一個塊hash來建立一個候選區塊。這個候選區塊的頭部作為模板分發給每個礦工。礦工用這個區塊模板在低於比特幣網路的難度下采礦,併傳送成功的結果返回礦池伺服器賺取份額。

10.11.2.2 P2P礦池

託管礦池存在管理人作弊的可能,管理人可以利用礦池進行雙重支付或使區塊無效。(參見“10.12 共識攻擊”) 此外,中 心化的礦池伺服器代表著單點故障。如果因為拒絕服務攻擊伺服器掛了或者被減慢,池中礦工就不能採礦。

在2011年, 為了解決由中心化造成的這些問題,提出和實施了一個新的礦池挖礦方法。P2Pool是一個點對點的礦池,沒有中心管理 人。P2Pool通過將礦池伺服器的功能去中心化,實現一個並行的類似區塊鏈的系統,名叫份額鏈(share chain)。

一個份額鏈是一個難度低 於比特幣區塊鏈的區塊鏈系統。份額鏈允許池中礦工在一個去中心化的池中合作,以每30秒一個份額區塊的速度在份額 鏈上採礦,並獲得份額。份額鏈上的區塊記錄了貢獻工作的礦工的份額,並且繼承了之前份額區塊上的份額記錄。當一 個份額區塊上還實現了比特幣網路的難度目標時,它將被廣播幷包含到比特幣的區塊鏈上,並獎勵所有已經在份額鏈區 塊中取得份額的池中礦工。

本質上說,比起用一個礦池伺服器記錄礦工的份額和獎勵,份額鏈允許所有礦工通過類似比特幣區塊鏈系統的去中心化的共識機制跟蹤所有份額。P2Pool採礦方式比在礦池中採礦要複雜的多,因為它要求礦工執行空間、記憶體、頻寬充足的專用計算機來支援一個位元 幣的完整節點和P2Pool節點軟體。P2Pool礦工連線他們的採礦硬體到本地P2Pool節點,它通過傳送區塊模板到礦機來 模擬一個礦池伺服器的功能。在P2Pool中,單獨的礦工建立自己的候選區塊,打包交易,非常類似於solo礦工,但是他 們在份額鏈上合作採礦。

P2Pool是一種比單獨挖礦有更細粒度收入優勢的混合方法。但是不需要像託管礦池那樣給管理 人太多權力。即使P2Pool減少了採礦池運營商的中心化程度,但也可能容易受到份額鏈本身的51%的攻擊。 P2Pool的廣泛採用並不能解決比特幣本身的51%攻擊問題。 相反,作為多樣化採礦生態系統的一部分,P2Pool整體使得比特幣更加強大。

10.12 共識攻擊

比特幣的共識機制指的是,被礦工(或礦池)試圖使用自己的算力實行欺騙或破壞的難度很大,至少理論上是這樣。就像我們前面講的,比特幣的共識機制依賴於這樣一個前提,那就是絕大多數的礦工,出於自己利益最大化的考慮,都會 通過誠實地挖礦來維持整個比特幣系統。然而,當一個或者一群擁有了整個系統中大量算力的礦工出現之後,他們就可 以通過攻擊比特幣的共識機制來達到破壞比特幣網路的安全性和可靠性的目的。

值得注意的是,共識攻擊只能影響整個區塊鏈未來的共識,或者說,最多能影響不久的過去幾個區塊的共識(最多影響 過去10個塊)。而且隨著時間的推移,整個比特幣塊鏈被篡改的可能性越來越低。

理論上,一個區塊鏈分叉可以變得很 長,但實際上,要想實現一個非常長的區塊鏈分叉需要的算力非常非常大,隨著整個比特幣區塊鏈逐漸增長,過去的區 塊基本可以認為是無法被分叉篡改的。同時,共識攻擊也不會影響使用者的私鑰以及加密演算法(ECDSA)。

共識攻擊也 不能從其他的錢包那裡偷到比特幣、不簽名地支付比特幣、重新分配比特幣、改變過去的交易或者改變比特幣持有紀 錄。共識攻擊能夠造成的唯一影響是影響最近的區塊(最多10個)並且通過拒絕服務來影響未來區塊的生成。共識攻擊的一個典型場景就是“51%攻擊”。想象這麼一個場景,一群礦工控制了整個比特幣網路51%的算力,他們聯合 起來打算攻擊整個比特幣系統。由於這群礦工可以生成絕大多數的塊,他們就可以通過故意製造塊鏈分叉來實現“雙重支 付”或者通過拒絕服務的方式來阻止特定的交易或者攻擊特定的錢包地址。

區塊鏈分叉/雙重支付攻擊指的是攻擊者通過 不承認最近的某個交易,並在這個交易之前重構新的塊,從而生成新的分叉,繼而實現雙重支付。有了充足算力的保 證,一個攻擊者可以一次性篡改最近的6個或者更多的區塊,從而使得這些區塊包含的本應無法篡改的交易消失。值得注意的是,雙重支付只能在攻擊者擁有的錢包所發生的交易上進行,因為只有錢包的擁有者才能生成一個合法的簽名用 於雙重支付交易。攻擊者在自己的交易上進行雙重支付攻擊,如果可以通過使交易無效而實現對於不可逆轉的購買行為不予付款, 這種攻擊就是有利可圖的。

讓我們看一個“51%攻擊”的實際案例吧。在第1章我們講到,Alice 和 Bob 之間使用比特幣完成了一杯咖啡的交易。咖啡 店老闆 Bob 願意在 Alice 給自己的轉賬交易確認數為零的時候就向其提供咖啡,這是因為這種小額交易遭遇“51%攻 擊”的風險和顧客購物的即時性(Alice 能立即拿到咖啡)比起來,顯得微不足道。這就和大部分的咖啡店對低於25美元 的信用卡消費不會費時費力地向顧客索要簽名是一樣的,因為和顧客有可能撤銷這筆信用卡支付的風險比起來,向使用者 索要信用卡簽名的成本更高。

相應的,使用比特幣支付的大額交易被雙重支付的風險就高得多了,因為買家(攻擊者) 可以通過在全網廣播一個和真實交易的UTXO一樣的偽造交易,以達到取消真實交易的目的。雙重支付可以有兩種方 式:要麼是在交易被確認之前,要麼攻擊者通過塊鏈分叉來完成。進行51%攻擊的人,可以取消在舊分叉上的交易記 錄,然後在新分叉上重新生成一個同樣金額的交易,從而實現雙重支付。

再舉個例子:攻擊者Mallory在Carol的畫廊買了描繪偉大的中本聰的三聯組畫(The Great Fire),Mallory通過轉賬價值25萬美金的比特幣 與Carol進行交易。在等到一個而不是六個交易確認之後,Carol放心地將這幅組畫包好,交給了Mallory。這時,Mallory 的一個同夥,一個擁有大量算力的礦池的人Paul,在這筆交易寫進區塊鏈的時候,開始了51%攻擊。

首先,Paul利用自己礦池的算力重新計算包含這筆交易的塊,並且在新塊裡將原來的交易替換成了另外一筆交易(比如直接轉給了Mallory 的另一個錢包而不是Carol的),從而實現了“雙重支付”。這筆“雙重支付”交易使用了跟原有交易一致的UTXO,但收款人被替換成了Mallory的錢包地址。

然後,Paul利用礦池在偽造的塊的基礎上,又計算出一個更新的塊,這樣,包含這 筆“雙重支付”交易的塊鏈比原有的塊鏈高出了一個塊。到此,高度更高的分叉區塊鏈取代了原有的區塊鏈,“雙重支付”交 易取代了原來給Carol的交易,Carol既沒有收到價值25萬美金的比特幣,原本擁有的三幅價值連城的畫也被Mallory白白 拿走了。

在整個過程中,Paul礦池裡的其他礦工可能自始至終都沒有覺察到這筆“雙重支付”交易有什麼異樣,因為挖礦程式都是自動在執行,並且不會時時監控每一個區塊中的每一筆交易。

為了避免這類攻擊,售賣大宗商品的商家應該在交易得到全網的6個確認之後再交付商品。或者,商家應該使用第三方 的多方簽名的賬戶進行交易,並且也要等到交易賬戶獲得全網多個確認之後再交付商品。一條交易的確認數越多,越難 被攻擊者通過51%攻擊篡改。

對於大宗商品的交易,即使在付款24小時之後再發貨,對買賣雙方來說使用比特幣支付也 是方便並且有效率的。而24小時之後,這筆交易的全網確認數將達到至少144個(能有效降低被51%攻擊的可能性)。共識攻擊中除了“雙重支付”攻擊,還有一種攻擊場景就是拒絕對某個特定的比特幣地址提供服務。一個擁有了系統中絕 大多數算力的攻擊者,可以輕易地忽略某一筆特定的交易。如果這筆交易存在於另一個礦工所產生的區塊中,該攻擊者 可以故意分叉,然後重新產生這個區塊,並且把想忽略的交易從這個區塊中移除。這種攻擊造成的結果就是,只要這名 攻擊者擁有系統中的絕大多數算力,那麼他就可以持續地干預某一個或某一批特定錢包地址產生的所有交易,從而達到 拒絕為這些地址服務的目的。

需要注意的是,51%攻擊並不是像它的命名裡說的那樣,攻擊者需要至少51%的算力才能發起,實際上,即使其擁有不 到51%的系統算力,依然可以嘗試發起這種攻擊。之所以命名為51%攻擊,只是因為在攻擊者的算力達到51%這個閾值 的時候,其發起的攻擊嘗試幾乎肯定會成功。

本質上來看,共識攻擊,就像是系統中所有礦工的算力被分成了兩組,一 組為誠實算力,一組為攻擊者算力,兩組人都在爭先恐後地計算塊鏈上的新塊,只是攻擊者算力算出來的是精心構造 的、包含或者剔除了某些交易的塊。因此,攻擊者擁有的算力越少,在這場決逐中獲勝的可能性就越小。

從另一個角度 講,一個攻擊者擁有的算力越多,其故意創造的分叉塊鏈就可能越長,可能被篡改的最近的塊或者或者受其控制的未來 的塊就會越多。一些安全研究組織利用統計模型得出的結論是,算力達到全網的30%就足以發動51%攻擊了。全網算力的急劇增長已經使得比特幣系統不再可能被某一個礦工攻擊,因為一個礦工已經不可能佔據全網哪怕的1%算 力。

但是中心化控制的礦池則引入了礦池操作者出於利益而施行攻擊的風險。礦池操作者控制了候選塊的生成,同時也控制哪些交易會被放到新生成的塊中。這樣一來,礦池操作者就擁有了剔除特定交易或者雙重支付的權力。如果這種權利被礦池操作者以微妙而有節制的方式濫用的話,那麼礦池操作者就可以在不為人知的情況下發動共識攻擊並獲益。但是,並不是所有的攻擊者都是為了利益。

一個可能的場景就是,攻擊者僅僅是為了破壞整個比特幣系統而發動攻擊, 而不是為了利益。這種意在破壞比特幣系統的攻擊者需要巨大的投入和精心的計劃,因此可以想象,這種攻擊很有可能 來自政府資助的組織。同樣的,這類攻擊者或許也會購買礦機,運營礦池,通過濫用礦池操作者的上述權力來施行拒絕 服務等共識攻擊。但是,隨著比特幣網路的算力呈幾何級數快速增長,上述這些理論上可行的攻擊場景,實際操作起來 已經越來越困難。

近期比特幣系統的一些升級,比如旨在進一步將挖礦控制去中心化的P2Pool挖礦協議,也都正在讓這 些理論上可行的攻擊變得越來越困難。毫無疑問,一次嚴重的共識攻擊事件勢必會降低人們對比特幣系統的信心,進而可能導致比特幣價格的跳水。然而,比 特幣系統和相關軟體也一直在持續改進,所以比特幣社群也勢必會對任何一次共識攻擊快速做出響應,以使整個比特幣 系統比以往更加穩健和可靠。

10.13改變共識規則

共識規則確定交易和塊的有效性。 這些規則是所有比特幣節點之間協作的基礎,並且負責將所有不同角色的本地視角融合到整個網路中的單一一致的區塊鏈中。雖然共識規則在短期內是不變的,並且在所有節點之間必須一致,但長期來看它們並不總是不變的。 為了演進和開發比特幣系統,規則必須隨時改變以適應新功能,改進或修復錯誤。 然而,與傳統軟體開發不同,升級到共識系統要困難得多,需要所有參與者之間的協調。

10.13.1硬分叉

在前面章節比特幣分叉中,我們研究了比特幣網路如何短暫地分叉,網路中的兩個部分在短時間內處於區塊鏈的兩個不同分支。我們看到這個過程是如何自然發生的,作為網路的正常執行的一部分,以及如何在一個或多個塊被挖掘之後,網路在一個統一的區塊鏈上重新收斂。

另一種情況是,網路也可能會分叉到兩條鏈條,這是由於共識規則的變化。這種分叉稱為硬分叉,因為這種分叉後,網路不會重新收斂到單個鏈路上。相反,這兩條鏈子獨立發展。當比特幣網路的一部分節點按照與網路的其餘部分節點不同的一致性規則執行時,硬分叉就會發生。

這可能是由於錯誤或者由於故意改變了協商一致規則的實施而發生的。硬分叉可用於改變共識的規則,但需要在系統中所有參與者之間進行協調。沒有升級到新的共識規則的任何節點都不能參與共識機制,並且在硬分叉時刻被強制到單獨的鏈上。

因此,硬分叉引入的變化可以被認為不是“向前相容”,因為未升級的系統不能再處理新的共識規則。讓我們來看一下硬分叉的技巧和具體的例子。下圖10-9顯示區塊鏈出現兩個分叉。在塊高度4處,發生單一個區塊分叉。這是我們在前面章節比特幣分叉中看到的自發分叉的型別。經過塊5的挖掘,網路在一條鏈上重新收斂,分叉被解決。

在這裡插入圖片描述
然而,後來在塊高度6處發生了硬分叉。我們假設原因是由於新共識規則的變化出現新的客戶端版本。從塊高度7開始,礦工執行新的版本,需要接受新型別的數字簽名,我們稱之為“Smores”簽名,它不是基於ECDSA的簽名。緊接著,執行新版本的節點建立了一筆包含Smores簽名的交易,一個更新了軟體的礦工挖礦出了包含此交易的區塊7b。

任何尚未升級軟體以驗證Smores簽名的節點或礦工現在都無法處區塊7b。從他們的角度來看,包含Smores簽名的交易和包含該交易的塊7b的交易都是無效的,因為它們是根據舊的共識規則進行評估的。

這些節點將拒絕該筆交易和區塊,並且不會傳播它們。正在使用舊規則的任何礦工都不接受塊7b,並且將繼續挖掘其父級為塊6的候選塊。實際上,使用如果它們連線的所有節點都是也遵守舊的規則,遵守舊規則的礦工甚至可能接收不到塊7b,因此不會傳播這個區塊。最終,他們將能夠開採區塊7a,這個舊的規則是有效的,其中不包含與Smores簽名的任何交易。

這兩個鏈條從這一點繼續分歧。 “b”鏈的礦工將繼續接受並開採含有Smores簽名的交易,而“a”鏈上的礦工將繼續忽視這些交易。即使塊8b不包含任何Smores簽名的交易,“a”鏈上的礦工也無法處理。對他們來說,它似乎是一個孤立的塊,因為它的父“7b”不被識別為一個有效的塊。

10.13.2硬分叉:軟體,網路,採礦和鏈

對於軟體開發人員來說,術語“分叉”具有另一個含義,對“硬分叉”一詞增加了混淆。在開源軟體中,當一組開發人員選擇遵循不同的軟體路線圖並啟動開源專案的競爭實施時,會發生叉。我們已經討論了兩種情況,這將導致硬分叉:共識規則中的錯誤,以及對共識規則的故意修改。在故意改變共識規則的情況下,軟體叉在硬分叉之前。但是,對於這種型別的硬分叉,新的共識規則必須通過開發,採用和啟動新的軟體實現。

試圖改變共識規則的軟分叉的例子包括Bitcoin XT,Bitcoin Classic和最近的Bitcoin Unlimited。但是,這些軟分叉都沒有產生硬分叉。雖然軟體叉是一個必要的前提條件,但它本身不足以發生硬分叉。對於硬分叉發生,必須是由於採取相互競爭的實施方案,並且規則需要由礦工,錢包和中間節點啟用。相反,有許多比特幣核心的替代實現方案,甚至還有軟分叉,這些沒有改變共識規則,阻止發生錯誤,可以在網路上共存並互操作,最終並未導致硬分叉。

不同的共識規則在交易或塊的驗證中會以明確的和清晰的方式有所不同。這種差別可能以微妙的方式表現,比如共識規則適用於比特幣指令碼或加密原語(如數字簽名)時。最後,共識規則的差別還可能會由於意料之外的方式,比如由於系統限制或實現細節所產生的隱含共識約束。在將Bitcoin Core 0.7升級到0.8之前的意料之中的硬分叉中看到了後者的一個例子,這是由於用於儲存塊的Berkley DB實現的限制引起的。

從概念上講,我們可以將硬分叉子看成四個階段:軟分叉,網路分叉,挖礦分叉和區塊鏈分叉。該過程開始於開發人員建立的客戶端,這個客戶端對共識規則進行了修改。當這種新版本的客戶端部署在網路中時,一定百分比的礦工,錢包使用者和中間節點可以採用並執行該版本客戶端。得到的分叉將取決於新的共識規則是否適用於區塊,交易或系統其他方面。如果新的共識規則與交易有關,那麼當交易被挖掘成一個塊時,根據新規則建立交易的錢包可能會產生出一個網路分叉,這就是一個硬分叉。如果新規則與區塊有關,那麼當一個塊根據新規則被挖掘時,硬分叉程式將開始。

首先,是網路分叉。基於舊的共識規則的節點將拒絕根據新規則建立的任何交易和塊。此外,遵循舊的共識規則的節點將暫時禁止和斷開傳送這些無效交易和塊的任何節點。因此,網路將分為兩部分:舊節點將只保留連線到舊節點,新節點只能連線到新節點。基於新規則的單個交易或塊將通過網路波動,實現分割槽,導致出現兩個網路。

一旦使用新規則的礦工開採了一個塊,挖礦和區塊鏈也將分叉。新的礦工將在新區塊之上挖掘,而老礦工將根據舊的規則挖掘一個單獨的鏈條。處於分割槽的網路將使得按照各自共識規則執行的礦工將不會接收彼此的塊,因為它們連線到兩個單獨的網路。

10.13.3分離礦工和難度

隨著礦工們被分裂開始開採兩條不同的鏈條,鏈上的雜湊算力也被分裂。兩個鏈之間的挖礦能力可以分成任意比例。新的規則可能只有少數人跟隨,或者也可能是絕大多數礦工。

我們假設,例如,80%-20%的分割,大多數挖礦能力使用新的共識規則。我們還假設分叉在改變目標(retarget)後立即出現。這兩條鏈將從改變目標之後各自接受自己的難度。新的共識規則擁有80%的以前可用的挖礦權的承諾。從這個鏈的角度來看,與上一週期相比,挖礦能力突然下降了20%。區塊將會平均每12分鐘發現一次,這意味著可以擴大這條鏈的挖礦能力下降了20%。這個塊發行速度將持續下去(除非有任何改變雜湊功率的因素出現),直到2016塊開採,這將需要大約24,192分鐘(每個塊需要12分鐘)或16.8天。 16.8天后,基於此鏈中雜湊算力的減少,改變目標將再次發生,並將難度調整(減少20%)每10分鐘產生一個區塊。

少數人認可的那條鏈,根據舊規則繼續挖礦,現在只有20%的雜湊算力,將面臨更加艱鉅的任務。在這條鏈上,平均每隔50分鐘開採一次。這個難度將不會在2016個塊之前進行調整,這將需要100,800分鐘,或大約10周的時間。假設每個塊具有固定容量,這也將導致交易容量減少5倍,因為每小時可用於記錄交易的塊大幅減少了。

10.13.4有爭議的硬叉

這是共識軟體開發的黎明。正如開源開發改變了軟體的方法和產品,創造了新的方法論,新工具和新社群,共識軟體開發也代表了電腦科學的新前沿。在比特幣發展路線圖的辯論,實驗和糾結之中,我們將看到新的開發工具,實踐,方法和社群的出現。硬分叉被視為有風險,因為它們迫使少數人被迫選擇升級或是必須保留在少數派鏈條上。將整個系統分為兩個競爭系統的風險被許多人認為是不可接受的風險。結果,許多開發人員不願使用硬分叉機制來實現對共識規則的升級,除非整個網路都能達成一致。

任何沒有被所有人支援的硬分叉建議也被認為是“有爭議”的,不願冒險分裂系統。硬分叉的問題在比特幣開發社群也是非常有爭議的,尤其是與控制區塊大小限制的共識規則的任何提議相關。一些開發商反對任何形式的硬叉,認為它太冒險了。另一些人認為硬分叉機制是提升共識規則的重要工具,避免了“技術債務”,並與過去提供了一個乾淨的了斷。

最後,有些開發商看到硬分叉作為一種應該很少使用的機制,應該經過認真的計劃,並且只有在近乎一致的共識下才建議使用。我們已經看到出現了新的方法來解決硬分叉的危險。在下一節中,我們將看一下軟分叉,以及BIP-34和BIP-9訊號和啟用共識修改的方法。

10.13.5軟分叉

並非所有共識規則的變化都會導致硬分叉。只有前向不相容的共識規則的變化才會導致分叉。如果共識規則的改變也能夠讓未修改的客戶端仍然按照先前的規則對待交易或者區塊,那麼就可以在不進行分叉的情況下實現共識修改。這就是軟分叉,來區分之前的硬分叉。實際上軟分叉不是分叉。軟分叉是與共識規則的前向相容並作些變化,允許未升級的客戶端程式繼續與新規則同時工作。

軟分叉的一個不是很明顯的方面就是,軟分叉級只能用於增加共識規則約束,而不是擴充套件它們。為了向前相容,根據新規則建立的交易和塊也必須在舊規則下有效,但是反之亦然。新規則只能增加限制,否則,根據舊規則建立的交易和區塊被拒絕時,還是會將觸發硬分叉。

軟叉可以通過多種方式實現,該術語不定義單一方法,而是一組方法,它們都有一個共同點:它們不要求所有節點升級或強制非升級節點必須脫離共識。

10.13.5.1軟分叉重新定義NOP操作碼

基於NOP操作碼的重新解釋,在比特幣中實現了一些軟分叉。 Bitcoin指令碼有10個操作碼保留供將來使用,NOP1到NOP10。 根據共識規則,這些操作碼在指令碼中的存在被解釋為無效的運算子,這意味著它們沒有任何效果。 在NOP操作碼之後繼續執行,就好像它不存在一樣。因此,軟叉可以修改NOP程式碼的語義給它新的含義。

例如,BIP-65(CHECKLOCKTIMEVERIFY)重新解釋了NOP2操作碼。 實施BIP-65的客戶將NOP2解釋為OP_CHECKLOCKTIMEVERIFY,並在其鎖定指令碼中包含該操作碼的UTXO上,強制了絕對鎖定的共識規則。 這種變化是一個軟分叉,因為在BIP-65下有效的交易在任何沒有實現(不瞭解)BIP-65的客戶端上也是有效的。 對於舊的客戶端,該指令碼包含一個NOP程式碼,這被忽略。

10.13.5.2其他方式軟分叉升級

NOP操作碼的重新解釋既是計劃的,也是共識升級的明顯機制。然而,最近,引入了另一種軟分叉機制,其不依賴於NOP操作碼,用於非常特定型別的共識改變。這在隔離見證掌機[segwit]中有更詳細的檢查。 Segwit是一個交易結構的體系結構變化,它將解鎖指令碼(見證)從交易內部移動到外部資料結構(將其隔離)。

Segwit最初被設想為硬分叉升級,因為它修改了一個基本的結構(交易)。在2015年11月,一位從事比特幣核心工作的開發人員提出了一種機制,通過這種機制,可以將軟體包引入segwit。用於此的機制是在segwit規則下建立的UTXO的鎖定指令碼的修改,使得未修改的客戶端將任何解鎖指令碼視為可鎖定指令碼。

因此,可以引入segwit就是軟分叉,而不需要每個節點必須從鏈上升級或拆分網路。有可能還有其他尚未被發現的機制,通過這種機制可以以向前相容的方式進行升級,都作為軟分叉。

10.13.6對軟分叉的批評

基於NOP操作碼的軟分叉是相對無爭議的。 NOP操作碼被放置在比特幣指令碼中,明確的目標是允許無中斷升級。然而,許多開發人員擔心軟分叉升級的其他方法會產生不可接受的折衷。

對軟分叉更改的常見批評包括:

  • 技術性債務

由於軟叉在技術上比硬叉升級更復雜,所以引入了技術性債務,這是指由於過去的設計權衡而增加程式碼維護的未來成本。程式碼複雜性又增加了錯誤和安全漏洞的可能性。

  • 驗證放鬆

未經修改的客戶端將交易視為有效,而不評估修改的共識規則。實際上,未經修改的客戶端不會使用全面的協商一致的規則來驗證,因為它們對新規則無視。這適用於基於NOP的升級,以及其他軟分叉升級。

  • 不可逆轉升級

因為軟分叉產生額外的共識約束的交易,所以它們在實踐中成為不可逆轉的升級。如果軟分叉升級在被啟用後被回退,根據新規則建立的任何交易都可能導致舊規則下的資金損失。例如,如果根據舊規則對CLTV交易進行評估,則不存在任何時間鎖定約束,並且可以花費在任何時間。因此,評論家認為,由於錯誤而不得不被回退的失敗的軟分叉幾乎肯定會導致資金的流失。

10.14使用區塊版本發出軟分叉訊號

由於軟分叉允許未經修改的客戶在協商一致的情況下繼續運作,“啟用”軟分叉的機制是通過向礦工發出訊號準備:大多數礦工必須同意他們準備並願意執行新的共識規則。 為了協調他們的行動,有一個訊號機制,使他們能夠表達他們對共識規則改變的支援。 該機制是在2013年3月啟用BIP-34並在2016年7月被BIP-9啟用所取代。

10.14.1 BIP-34訊號和啟用

在BIP-34中的第一個實施使用塊版本欄位來允許礦工表示準備達成特定的共識規則更改。在BIP-34之前,按照慣例將塊版本設定為“1”,而不是以共識方式執行。BIP-34定義了一個共識規則變更,要求將Coinbase交易的coinbade欄位(輸入)包含塊高度。在BIP-34之前,Coinbase可以讓礦工選擇包含的任意資料。在BIP-34啟用之後,有效塊必須在Coinbase的開始處包含特定的塊高度,並且使用大於或等於“2”的版本號進行標識。

為了標記BIP-34的更改和啟用,礦工們將塊版本設定為“2”而不是“1”。這沒有立即使版本“1”塊無效。一旦啟用,版本“1”塊將變得無效,並且將需要所有版本“2”塊以包含Coinbase庫中的塊高度才能有效。

BIP-34基於1000個塊的滾動視窗定義了兩步啟動機制。礦工將以“2”作為版本號來構建塊,從而向BIP-34發出訊號。嚴格來說,由於共識規則尚未被啟用,這些區塊還沒有遵守新的共識規則,也就是將塊高度包括在Coinbase交易中。

共識規則分為兩個步驟啟用:

如果75%(最近1000個塊中的750個)標有版本“2”,則版本“2”塊必須包含coinbase交易中的塊高度,否則被拒絕為無效。 版本“1”塊仍然被網路接受,不需要包含塊高度。 這個新時期的新舊共識規則共存。

當95%(最近1000塊中的950)是版本“2”時,版本“1”塊不再被視為有效。 版本“2”塊只有當它們包含coinbase中的塊高度(根據先前閾值)時才有效。 此後,所有塊必須符合新的一致性規則,所有有效塊必須包含coinbase交易中的塊高度。根據BIP-34規則成功發訊號和啟用後,該機制再次使用兩次以啟用軟分叉:BIP-66標籤的嚴格DER編碼通過BIP-34訊號通過塊版本“3”啟用,無效版本“2”塊。BIP-65 CHECKLOCKTIMEVERIFY被塊版本“4”的BIP-34訊號啟用,無效版本“3”塊。

BIP-65啟用後,BIP-34的訊號和啟用機制退出,並用下面描述的BIP-9訊號傳導機制代替。這個標準在BIP-34(Block v2, Height in Coinbase)中定義。

10.14.2 BIP-9訊號和啟用

BIP-34,BIP-66和BIP-65使用的機制成功地啟用了三個軟分叉。 然而,它又被替換了,因為它有幾個限制:

通過使用塊版本的整數值,一次只能啟用一個軟分叉,因此需要軟分叉提議之間的協調以及對其優先順序和排序的協議。

此外,由於塊版本增加,所以機制並沒有提供一種直接的方式來拒絕變更,而是提出一個不同的方法。 如果老客戶仍然在執行,他們可能會錯誤地將訊號轉換為新的更改,作為以前拒絕的更改的訊號。

每個新的更改不可逆轉地減少可用的供將來更改的塊版本。

提出BIP-9來克服這些挑戰,提高實施未來變化的速度和便利性。

BIP-9將塊版本解釋為bit欄位而不是整形(int)欄位。因為塊版本最初作為整形(int32)欄位,因此版本1到4,只有29位可用作bit欄位。這留下29位,可以獨立使用,同時在29個不同的提案上表示準備就緒。BIP-9還設定了信令和啟用的最大時間。礦工們不需要永遠發出訊號。如果提案在TIMEOUT期間(在提案中定義)未啟用,則該提案被視為被拒絕。該提議可以使用不同位的信令重新提交,更新啟用週期。

此外,在TIMEOUT已經過去並且特徵被啟用或被拒絕之後,信令位可以被再次用於另一個特徵而不會混淆。因此,多達29次更改可以並行發出訊號,TIMEOUT後可將這些位“再迴圈”以提出新的更改。

注意雖然信令位可以重複使用或回收利用,但只要投票期間不重疊,BIP-9的作者就建議僅在必要時重複使用位; 主要是由於舊軟體中的bug,可能會發生意外的行為。 總之,我們不應該期望在所有29位都被使用一次之前看到重用。

建議的更改由包含以下欄位的資料結構標識:名稱用於區分提案的簡短描述。 通常,BIP將該提案描述為“bipN”,其中N是BIP編號。位0到28,礦工使用的塊版本中的位用於表示此提案的批准。開始時間訊號開始的時間(基於中值時間過去或MTP),之後該位的值被解釋為提示的信令準備。時間結束該時間(基於MTP),如果尚未達到啟用閾值,則認為該更改被拒絕。

與BIP-34不同,BIP-9根據2016塊的難度改變目標(retarget)週期,在整個間隔週期中計算啟用訊號的數量。 在每個難度調整週期內,如果提案的啟用訊號數量總和超過95%(2016中的1916),則該提案將在下一個難度調整週期內啟用。BIP-9提供了一個提案狀態圖,以說明一個提案的各個階段和轉換,如圖10-10所示。

在這裡插入圖片描述

一旦它們的引數在比特幣軟體中被知道(定義),提案將在DEFINED狀態下開始。 對於具有MTP的塊在開始時間之後,提議狀態轉換到STARTED。 如果在改變目標期間超過了投票閾值,並且未超過超時,則提案狀態轉換為LOCKED_IN。 一個改變目標期後,該提案變為活動。

一旦達到這個狀態,提案仍然處於活動狀態。 如果在達到投票閾值之前超時時間,提案狀態將更改為“已故”,表示已拒絕的提案。 REJECTED的建議永遠在這個狀態。BIP-9首次實施用於啟用CHECKSEQUENCEVERIFY和相關BIP(68,112,113)。 名為“csv”的建議在2016年7月成功啟動。標準定義在BIP-9(Version bits with timeout and delay).

10.15共識軟體開發

共識軟體開發不斷髮展,對於改變共識規則的各種機制進行了很​​多討論。由於其本質,比特幣在協調和變化共識方面樹立了非常高的標杆。作為一個去中心化的制度,不存在將權力強加於網路參與者的“權威”。權力分散在多個支持者,如礦工,核心開發商,錢包開發商,交易所,商家和終端使用者之間。這些支持者不能單方面做出決定。例如,礦工在理論上可以通過簡單多數(51%)來改變規則,但受到其他支持者的同意的限制。

如果他們單方面行事,其他參與者可能會拒絕遵守,將經濟活動保持在少數鏈條上。沒有經濟活動(交易,商人,錢包,交易所),礦工們將用空的塊開採一個無價值的貨幣。這種權力的擴散意味著所有參與者必須協調,或者不能做出任何改變。現狀是這個制度的穩定狀態,只有在很大程度上達成一致的情況下,才能有幾個變化。軟分叉的95%門檻反映了這一現實。

重要的是要認識到,對於共識發展沒有完美的解決辦法。硬分叉和軟分叉都涉及權衡。對於某些型別的更改,軟分叉可能是一個更好的選擇;對於別人來說,硬分叉可能是一個更好的選擇。沒有完美的選擇,都帶有風險。共識軟體開發的一個不變特徵是變革是困難的,是共識力量的妥協。有些人認為這是共識制度的弱點。在時間上,你可以像我一樣看到它,把它當作這個系統最大的優勢。

保全比特幣是很有挑戰性的事,因為比特幣不像銀行賬戶餘額那樣體現抽象價值。比特幣其實更像數字現金或黃金。 你可能聽過這樣的說法,“誰持有幾乎等同法律擁有。”好吧,在比特幣的世界裡,誰持有誰擁有。持有擁有解鎖比特幣的金鑰就相當於持有現金或一塊貴重金屬。你可能會將金鑰丟失,會放錯地方,會被盜或者不小心錯支了數額。無論是哪種場景,使用者都沒有辦法撤回,因為這就像是將現金丟在了車水馬龍的大街上。

不過,與現金、黃金或者銀行賬戶相比,比特幣有著一個獨一無二的優勢。你不能“備份”你的現金、黃金或者銀行賬 戶,但你可以像備份其他檔案一樣,備份含有金鑰的比特幣錢包。它可以被複製成很多份,放到不同的地方儲存起來, 甚至能列印到紙上進行實體備份。比特幣與至今為止的其他貨幣是如此不同,以致於我們需要以一種全新的思維方式來 衡量比特幣的安全性。

相關文章