比特幣後端全解析

買賣虛擬貨幣
在上節課的學習中,我們瞭解到比特幣前端的組成,今天這節課,我們繼續“拆解”比特幣,從比特幣中一觀密碼貨幣的“後端”是什麼樣子。比特幣節點後端負責參與比特幣網路的通訊互聯,維護區塊鏈,驗證區塊、交易,廣播、轉播傳遞區塊交易資訊。比特幣的後臺程式主要是由bitcoind,以及挖礦節點程式構成。比特幣核心bitcoin-qt實際上是包含前後端(除挖礦功能以外)的一體化節點。1. 比特幣節點後端-區塊鏈管理區塊鏈管理的程式碼邏輯都在main.cpp程式中實現。主要包括4個部分:下載區塊鏈:在比特幣全節點第一次加入網路執行時,先要下載並驗證整條區塊鏈. “區塊報頭先行”(header first),一個初始區塊鏈下載方式,新的節點先從鄰節點下載所有的區塊報頭,再並行地從多個鄰節點同時下載不同區間的區塊大大提升了整條區塊鏈的下載速度。接收區塊鏈:現有節點在開啟時(非第一次)會先將整個區塊鏈的索引從LevelDB調進記憶體.需要注意的是,該區塊鏈的索引不是單條的鏈,而可能是一個樹,也就是說每個區塊只有一個父區塊,但可能有多個子區塊,因為子區塊形成暫時分叉,需要逐漸發現哪個子區塊屬於最長的鏈條。

區塊鏈驗證:在區塊鏈管理中,連線區塊函式ConnectBlock()是一個檢測“雙花”交易的關鍵.該函式對新接收的區塊中的所有交易進行檢測,驗證是否每個交易的比特幣來源都能在當前的“尚未花比特幣”(Unspend Transaction Output,UTXO)記錄中找到匹配。

重組區塊鏈:當節點發現網路中有一條不基於它當前區塊鏈的一條更長區塊鏈時,它需要斷開現有的區塊並對區塊鏈進行重組。

大部分重組只是一個區塊的重組,多發生在不同礦工幾乎同時挖到合法的區塊時。

斷開區塊、重組區塊鏈涉及UTXO更改,被斷開的區塊中交易會回退到交易記憶體池,這個時候“回滾”記錄就可以用來回滾斷開區塊中的交易。

2.  比特幣節點後端-區塊驗證

· 交易驗證模組會基於以下條件檢查收到的比特幣交易是否合規:
· 交易的格式是資料結構必須正確
· 交易的輸入和輸出不能為空;(Coinbase交易沒有輸入)。
· 交易的大小不能超過定義的區塊最大值MAX_BLOCK_SIZE(最早為1MB,後來逐漸調整)。
· 每個交易的輸出,以及所有輸出的合計,必須在一定範圍內,也就是大於0,小於2100萬。
· 交易輸入的雜湊值不能為零,不應該轉Coinbase交易。
· nLockTime小於等於INT_MAX。
· 交易的位元組大小要等於或大於100。
· 交易的簽名運算元要小於簽名操作的上限。
· 鎖定指令碼(scriptPubkey)必須是標準格式。
· 和收到的交易相匹配的交易必須能在當前交易池或是主鏈上某個區塊中找到。
· 對交易的每個輸入,如果其對應的UTXO輸出能在當前交易池中找到,該交易必須拒絕(雙花交易),因為當前交易池是未經記錄在區塊鏈中的· 交易,而交易的每個輸入應該來自確認的UTXO,如果在當前交易池中找到,那就是雙花交易。
· 對交易的每個輸入,如果其對應的UTXO輸出不能在主鏈或當前交易池中找到,該交易是一個孤兒交易,應將該交易放入孤兒交易池中。
· 對交易中的每個輸入,如果其對應的UTXO輸出是一個挖礦初始(coinbase)交易,該初始交易應該獲得100個確認區塊的確認(普通交易是6個確認區塊的確認)。
· 對交易中的每個輸入,其對應的輸出必須是UTXO(存在且沒被花掉)。
· 用對應的輸出交易來獲得輸入的值,檢查每個輸入的值及其合計,應該在允許的區間(0~2100萬比特幣)。
· 如果一個交易的輸入合計小於輸出總計,則拒絕該交易。
· 如果交易的費用太低,則拒絕該交易。
· 每個輸入的解鎖指令碼(unlocking script)必須和相應輸出的鎖定指令碼(locking script)共同驗證交易的合規性。

最後這個檢查是比特幣平臺設計的精髓,比特幣的一個很大的創新是依靠指令碼來驗證交易的合法性,即每一個將要花掉的比特幣必須有相應的來源。

鎖定指令碼是由一連串堆疊命令和公鑰雜湊組成,公鑰雜湊即RIPEMD160(SHA256(公鑰)),大小20位元組;鎖定指令碼格式為:

OP_DUP OP_HASh260 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG 

解鎖指令碼是由簽名與公鑰組成,這就保證了必須擁有私鑰的使用者才能對某一筆交易進行解鎖.解鎖指令碼格式為:<Sig> <PubKey>

比特幣是將解鎖指令碼和鎖定指令碼串起來一同執行,如果最後結果是邏輯值“真”(TRUE),則驗證該交易的比特幣來源是合法的. 指令碼執行的過程和每個指令碼執行後的堆疊狀態如下:

· 由於是堆疊式計算引擎,因此作為數值的<sig>和<pubKey>相繼入棧.當執行指令碼OP_DUP時,它將堆疊頭的<pubKey>複製一份也壓入堆疊。

· 指令碼OP_HASh260執行,將把堆疊頭的<pubKey>彈出,並用HASh260演算法進行雜湊處理,雜湊結果放回堆疊.然後遇到<pubKeyHash?>數值,該數值也被壓入堆疊。

· 指令碼OP_EQUALVERIFY把堆疊頭端的兩個雜湊值都彈出,並進行比較.如果不一樣,驗證就出錯,交易不合法.如果驗證透過,堆疊只剩下<sig>和<pubKey>。

· 最後指令碼OP_CHECKSIG將兩者彈出,執行檢查簽名的指令碼,驗證該交易簽名是否是由擁有該公鑰對應使用者用其私鑰籤的,如果是,交易就是合法交易,否則就是不合法交易。

3.  比特幣節點後端-記憶體池管理

比特幣記憶體池(mempool)管理也就是交易池管理。

節點將透過驗證的交易放在一個交易池中,準備放在一個挖到的區塊中。當準備挖礦時,它按一定的優先順序次序從交易池中選出交易。

優先順序是按交易中的輸入對應的UTXO的“鏈齡”和交易額的大小來劃分的。越老UTXO的交易以及交易額越大的交易優先順序會越高。

優先順序(Priority)採用以下公式計算:Priority=Sum(Value of input×InputAge)/ Transaction Size,其中Value of input是按比特幣基礎單位(satoshi,聰)計算,1個satoshi等於一億分之一(10-8)個比特幣。InputAge按已在鏈上記錄該交易的區塊為起點,按後面有多少個區塊來計算,也就是計量該區塊在區塊鏈的“深度”。交易的大小以位元組為單位。

當區塊填滿後,剩下的交易會留在記憶體池,等待下一個區塊的到來。隨著它們的“鏈齡”的逐漸增加,它們以後被選中的機率也會逐漸增加。

記憶體池的比特幣的交易不會過期,但是記憶體池的交易不儲存在硬碟上,當挖礦節點重啟時,記憶體池的交易會被清空。

如果在一定時間內一個交易一直不能被礦工包括在區塊鏈上,錢包軟體需要重新傳送該交易,可以附上較高的交易費,獲得優先權。

在一些比特幣節點也實現維護一個獨立的“孤兒”交易池。如果一個交易的輸入相對應的UTXO不能被找到,也就是沒有“父”交易,會被當作“孤兒”交易,暫時放在“孤兒”交易池。當父交易來到後,該“孤兒”交易會被從“孤兒”交易池移到記憶體池。 

4.  比特幣節點後端-鄰節點管理

當一個新比特幣節點做初始啟動(bootstrap)的時候,它需要發現網路中的其他節點,並與至少一個節點連線。

一般是與一個已知的節點在8333埠建立TCP連線。

連線的“握手”流程傳送一個版本資訊,包括:P2P協議版本,本節點支援的服務,當前時間,對方節點IP地址,本節點IP地址,比特幣軟體版本,以及本節點當前區塊鏈的長度。

對方節點收到握手資訊後會回覆一個收到確認的資訊。

新節點如何發現鄰節點:

第一個辦法是用一些“DNS種子”查詢DNS。

第二個辦法是直接把一個已知的鄰節點作為種子節點,然後透過它發現更多的鄰節點。

如果一個連線上一段時間內沒有資訊互動,節點會定期發一些資訊去維護連線。

如果一個節點和鄰節點的連線在超過90分鐘裡沒有聯絡,該鄰節點會被認為下線,節點會尋找一個新的鄰節點來進行連線。

比特幣網路能動態地調節節點的連線,以保證比特幣網路的正常執行。

5.  比特幣節點後端-共識管理

比特幣裡廣義的共識管理(Consensus)應該包括挖礦、區塊驗證和交易驗證規則。

由於比特幣的關鍵是在陌生P2P環境建立共識機制,因此共識管理至關重要。

在比特幣0.12.0版本中,一部分共識管理的程式碼已經移到consensus子目錄。

目前在consensus子目錄的共識程式有consensus.*、merkle.*、params.*和validation.*。

6.  比特幣節點後端-規則管理

比特幣的共識規則是所有節點都必須遵守的規則(policy),而每個節點可以採用一些共識規則以外的個性化規則(比如一個節點可以拒絕儲存、中轉大於200KB的交易)。這部分的規則由規則管理模組實現,目前放在policy子目錄中。

7.  比特幣節點後端-密碼模組

密碼模組(Crypto)主要是處理比特幣地址,採用RIMEMD和SHA-256演算法以及Base-58編碼來生成比特幣地址。

比特幣的公鑰是透過私鑰產生的,然後採用Secure Hash Algorithm(SHA)演算法SHA256和RACE Integrity Primitives Evaluation Message Digest(RIPEMD)演算法RIPEMD160對公鑰進行處理,最後透過Base58編碼形成比特幣地址。

8.  比特幣節點後端-簽名模組

比特幣採用橢圓曲線數字簽名演算法(ECDSA)來實現數字簽名以及生成公鑰。ECDSA是一種非對稱密碼演算法,是基於橢圓曲線離散對數問題困難性的一種簽名演算法。

9.  比特幣節點後端-指令碼引擎

比特幣的指令碼語言是一種專門設計的、與“Forth”類似的、基於堆疊的程式設計指令碼語言。

基於堆疊的語言的指令只按順序執行一次,也就是說沒有迴圈或跳轉指令, 因此指令碼的指令數可以決定一個程式執行時間的上限和所需的記憶體上限。

比特幣的指令碼語言非常小,只能有256個指令,每個指令是一個位元組長。這256個指令中,75個是保留指令,15個已廢棄。

指令碼引擎是校驗交易的運算平臺,從對解鎖指令碼和鎖定指令碼的自動執行校驗可以看出該引擎的重要作用。

新版本的比特幣將指令碼引擎放在script子目錄中,將來可以變成可插拔引擎,使得引入新的功能更強大的引擎更為方便,不影響原有比特幣的程式碼。

目前script子目錄裡有interpreter.*、script.*和standard.*程式.script.h定義了指令碼命令,interpreter.cpp解析、評估和較驗指令碼命令,standard.h定義了標準的交易。

比特幣通常使用的指令如下:

10.  比特幣節點後端-挖礦

比特幣最早的挖礦程式是cpuminer,是透過CPU來挖礦的。

挖礦裝置經過CPU、GPU、FPGA,到現在基本都使用ASIC挖礦裝置。

針對SHA256挖礦演算法最佳化的ASIC挖礦裝置比其他挖礦裝置有著效能上無可比擬的優勢。

Bfgminer程式支援FPGA和ASIC挖礦裝置,是目前比較流行的挖礦程式. 最新程式碼見: http://www.bfgminer.org/

那如何把網路的出塊速度穩定在10分鐘一個呢?比特幣網路是透過調整挖礦難度的目標來達到這個目的. 難度計算公式是: New Difficulty = Old Difficulty × (Actual Time of Last 2016 Blocks / 20160minutes)

11.  比特幣節點後端-HTTP/JSON RPC服務端

比特幣在啟動的時候,初始化程式init.cpp會啟動HTTP/JSON RPC服務端的執行緒組。

該元件對外提供HTTP和JSON RPC的介面,外部程式可以透過JSONRPC介面來調比特幣的API,達到控制比特幣節點的功能。

該介面預設是僅接收來自本機的客戶端的連線請求。

12.  比特幣節點後端-Berkeley DB和Level DB資料庫

Berkeley DB是一個開源的檔案資料庫(Sleepycat Software公司開發),介於關聯式資料庫與記憶體資料庫之間,使用方式與記憶體資料庫類似,它提供的是一系列直接訪問資料庫的函式,主要用來備份使用者的金鑰。

Berkeley DB是一個輕巧而又效能高的嵌入式資料庫,可以儲存任意型別的鍵/值對,可以為一個鍵儲存多個資料,可以支援數千個併發執行緒同時運算元據庫,支援最大256TB的資料.

Level DB用來儲存區塊的索引和UTXO(未花的比特幣交易輸出)記錄,是一個Google實現的非常高效的鍵值(Key Value)資料庫,目前的版本1.2能夠支援幾十億級別的資料量。

Level DB的資料是冗餘資料,可以用原始區塊鏈資料來重建.如果沒有Level DB的資料,比特幣的校驗和其他操作都會變得非常緩慢。

13.  比特幣節點後端-P2P網路管理

P2P網路管理的程式碼主要是在P2P網路上實現和其他鄰節點的通訊功能。這些通訊功能包括:發現鄰節點;連線並管理與鄰節點的Socket連線;與鄰節點交換不同的P2P訊息(包含區塊和交易);有時在特殊情況下,會禁止異常行為的鄰節點的連線。

大部分的P2P程式碼集中在net.h/net.cpp。鄰節點IP地址管理程式碼是addrman.h/addrman.cpp。地址管理程式把地址存放在peers.dat資料庫中,在啟動的時候再把它調入記憶體。

比特幣節點的預設配置是主動連線8個鄰節點,同時允許最多125個其他節點發起的連線請求。

比特幣防止拒絕服務攻擊(DoS)的方法主要是禁止異常行為鄰節點的連線。如果一個鄰節點傳送非常明顯的錯誤資訊,該連線將被斷開,而且其IP地址會被禁止,其重新連線申請會被拒絕。

比特幣節點按功能分有幾種:全功能節點、基礎全節點和SPV節點

全功能節點帶有錢包、RPC服務端,具有挖礦功能和進行節點校驗區塊和交易,並把區塊和交易中轉給與之相連線的鄰節點。

基礎全節點也做區塊和交易的交易和中轉,但不挖礦,不帶錢包和RPC服務端。

SPV(Simplify Payment Verification)節點信任別的節點來對區塊和交易作校驗。

14. 比特幣節點後端-佇列管理

比特幣採用Zero MQ作為訊息佇列管理和訊息分發工具。Zero MQ號稱是“史上最快的訊息佇列”,是基於C語言開發的。

ZMQ是一個簡單好用的傳輸層,提供像框架一樣的一個socket library,它使得Socket程式設計更加簡單、簡潔,效能更高。

ZMQ是一個訊息處理佇列庫,可在多個執行緒、核心和主機盒之間彈性伸縮。

ZMQ不是一個伺服器,更像一個底層的網路通訊庫,在Socket API之上做了一層封裝,將網路通訊、程序通訊和執行緒通訊抽象為統一的API介面。

比特幣後端組成就說到這裡啦,經歷了第一階段的演化,區塊鏈的發展進入了“可程式設計”時代,區塊鏈也逐漸往產品化、實用性發展,我們下一節課將講述區塊鏈2.0——可程式設計區塊鏈,敬請期待。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;