比特幣采用了基于國際互聯網(Internet)的P2P(peer-to-peer)網絡架構。P2P是指位于同一網絡中的每臺計算機都彼此對等,各個節點共同提供網絡服務,不存在任何“特殊”節點。每個網絡節點以“扁平(flat)”的拓撲結構相互連通。在P2P網絡中不存在任何服務端(server)、中央化的服務、以及層級結構。P2P網絡的節點之間交互運作、協同處理:每個節點在對外提供服務的同時也使用網絡中其他節點所提供的服務。P2P網絡也因此具有可靠性、去中心化,以及開放性。
比特幣所采用的P2P網絡架構不僅僅是選擇拓撲結構這樣簡單。比特幣被設計為一種點對點的數字現金系統,它的網絡架構既是這種核心特性的反映,也是該特性的基石。去中心化控制是設計時的核心原則,它只能通過維持一種扁平化、去中心化的P2P共識網絡來實現。
“比特幣網絡”是按照比特幣P2P協議運行的一系列節點的集合。除了比特幣P2P協議之外,比特幣網絡中也包含其他協議。例如Stratum協議就被應用于挖礦、以及輕量級或移動端比特幣錢包之中。
6.2 節點類型及分工
盡管比特幣P2P網絡中的各個節點相互對等,但是根據所提供的功能不同,各節點可能具有不同的分工。每個比特幣節點都是路由、區塊鏈數據庫、挖礦、錢包服務的功能集合。
一些節點保有一份完整的、最新的區塊鏈拷貝,這樣的節點被稱為“全節點”。全節點能夠獨立自主地校驗所有交易,而不需借由任何外部參照。另外還有一些節點只保留了區塊鏈的一部分,它們通過一種名為“簡易支付驗證(SPV)”的方式來完成交易驗證。這樣的節點被稱為“SPV節點”,又叫“輕量級節點”。在如上圖所示的全節點用例中,名為完整區塊鏈的藍色圓圈即表示了全節點區塊鏈數據庫功能。
挖礦節點通過運行在特殊硬件設備上的工作量證明(proof-of-work)算法,以相互競爭的方式創建新的區塊。一些挖礦節點同時也是全節點,保有區塊鏈的完整拷貝;還有一些參與礦池挖礦的節點是輕量級節點,它們必須依賴礦池服務器維護的全節點進行工作。
用戶錢包也可以作為全節點的一部分,這在桌面比特幣客戶端中比較常見。當前,越來越多的用戶錢包都是SPV節點,尤其是運行于諸如智能手機等資源受限設備上的比特幣錢包應用;而這正變得越來越普遍。
圖6-2描述了擴展比特幣網絡中最為常見的節點類型
6.3 擴展比特幣網絡
運行比特幣P2P協議的比特幣主網絡由大約7000-10000個運行著不同版本比特幣核心客戶端(Bitcoin Core)的監聽節點、以及幾百個運行著各類比特幣P2P協議的應用(例如BitcoinJ、Libbitcoin、btcd等)的節點組成。比特幣P2P網絡中的一小部分節點也是挖礦節點,它們競爭挖礦、驗證交易、并創建新的區塊。許多連接到比特幣網絡的大型公司運行著基于Bitcoin核心客戶端的全節點客戶端,它們具有區塊鏈的完整拷貝及網絡節點,但不具備挖礦及錢包功能。這些節點是網絡中的邊緣路由器(edge routers),通過它們可以搭建其他服務,例如交易所、錢包、區塊瀏覽器、商家支付處理(merchant payment processing)等。
圖6-3描述了擴展比特幣網絡,它包括了多種類型的節點、網關服務器、邊緣路由器、錢包客戶端以及它們相互連接所需的各類協議。
6.4 網絡發現
當新的網絡節點啟動后,為了能夠參與協同運作,它必須發現網絡中的其他比特幣節點。新的網絡節點必須發現至少一個網絡中存在的節點并建立連接。由于比特幣網絡的拓撲結構并不基于節點間的地理位置,因此各個節點之間的地理信息完全無關。在新節點連接時,可以隨機選擇網絡中存在的比特幣節點與之相連。
節點通常采用TCP協議、使用8333端口(該端口號通常是比特幣所使用的,除8333端口外也可以指定使用其他端口)與已知的對等節點建立連接。
新節點是如何發現網絡中的對等節點的呢?雖然比特幣網絡中沒有特殊節點,但是客戶端會維持一個列表,那里列出了那些長期穩定運行的節點。這樣的節點被稱為“種子節點(seed nodes)”。新節點并不一定需要與種子節點建立連接,但連接到種子節點的好處是可以通過種子節點來快速發現網絡中的其他節點。在比特幣核心客戶端中,是否使用種子節點是通過“-dnsseed”控制的。默認情況下,該選項設為1,即意味著使用種子節點。另一種方式是,起始時將至少一個比特幣節點的IP地址提供給正在啟動的節點(該節點不包含任何比特幣網絡的組成信息)。在這之后,啟動節點可以通過后續指令建立新的連接。用戶可以使用命令行參數“-seednode”把啟動節點“引薦”并連接到一個節點,并將該節點用作DNS種子。在初始種子節點被用于形成“引薦”信息之后,客戶端會斷開與它的連接、并與新發現的對等節點進行通信。
當建立一個或多個連接后,新節點將一條包含自身IP地址的addr消息發送給其相鄰節點。相鄰節點再將此條addr消息依次轉發給它們各自的相鄰節點,從而保證新節點信息被多個節點所接收、保證連接更穩定。另外,新接入的節點可以向它的相鄰節點發送getaddr消息,要求它們返回其已知對等節點的IP地址列表。通過這種方式,節點可以找到需連接到的對等節點,并向網絡發布它的消息以便其他節點查找。
節點必須連接到若干不同的對等節點才能在比特幣網絡中建立通向比特幣網絡的種類各異的路徑(path)。由于節點可以隨時加入和離開,通訊路徑是不可靠的。因此,節點必須持續進行兩項工作:在失去已有連接時發現新節點,并在其他節點啟動時為其提供幫助。節點啟動時只需要一個連接,因為第一個節點可以將它引薦給它的對等節點,而這些節點又會進一步提供引薦。一個節點,如果連接到大量的其他對等節點,這既沒必要,也是對網絡資源的浪費。在啟動完成后,節點會記住它最近成功連接的對等節點;因此,當重新啟動后它可以迅速與先前的對等節點網絡重新建立連接。如果先前的網絡的對等節點對連接請求無應答,該節點可以使用種子節點進行重啟動。
如果已建立的連接沒有數據通信,所在的節點會定期發送信息以維持連接。如果節點持續某個連接長達90分鐘沒有任何通信,它會被認為已經從網絡中斷開,網絡將開始查找一個新的對等節點。因此,比特幣網絡會隨時根據變化的節點及網絡問題進行動態調整,不需經過中心化的控制即可進行規模增、減的有機調整。
6.5 全節點
全節點是指維持包含全部交易信息的完整區塊鏈的節點。完整區塊鏈節點保有完整的、最新的包含全部交易信息的比特幣區塊鏈拷貝,這樣的節點可以獨立地進行建立并校驗區塊鏈,從第一區塊(創世區塊)一直建立到網絡中最新的區塊。完整區塊鏈節點可以獨立自主地校驗任何交易信息,而不需要借助任何其他節點或其他信息來源。完整區塊節點通過比特幣網絡獲取包含交易信息的新區塊更新,在驗證無誤后將此更新合并至本地的區塊鏈拷貝之中。
6.6 交換“庫存清單”
一個全節點連接到對等節點之后,第一件要做的事情就是構建完整的區塊鏈。如果該節點是一個全新節點,那么它就不包含任何區塊鏈信息,它只知道一個區塊——靜態植入在客戶端軟件中的創世區塊。新節點需要下載從0號區塊(創世區塊)開始的數十萬區塊的全部內容,才能跟網絡同步、并重建全區塊鏈。
6.7 簡易支付驗證 (SPV)節點
并非所有的節點都有能力儲存完整的區塊鏈。許多比特幣客戶端被設計成運行在空間和功率受限的設備上,如智能電話、平板電腦、嵌入式系統等。對于這樣的設備,通過簡化的支付驗證(SPV)的方式可以使它們在不必存儲完整區塊鏈的情況下進行工作。這種類型的客端被稱為SPV客戶端或輕量級客戶端。
SPV節點只需下載區塊頭,而不用下載包含在每個區塊中的交易信息。由此產生的不含交易信息的區塊鏈,大小只有完整區塊鏈的1/1000。SPV節點不能構建所有可用于消費的UTXO的全貌,這是由于它們并不知道網絡上所有交易的完整信息。SPV節點驗證交易時所使用的方法略有不同,這個方法需依賴對等節點“按需”提供區塊鏈相關部分的局部視圖。
簡易支付驗證是通過參考交易在區塊鏈中的深度,而不是高度,來驗證它們。一個擁有完整區塊鏈的節點會構造一條驗證鏈,這條鏈是由沿著區塊鏈按時間倒序一直追溯到創世區塊的數千區塊及交易組成。而一個SPV節點會驗證所有區塊的鏈(但不是所有的交易),并且把區塊鏈和有關交易鏈接起來。
例如,一個全節點要檢查第300,000號區塊中的某個交易,它會把從該區塊開始一直回溯到創世區塊的300,000個區塊全部都鏈接起來,并建立一個完整的UTXO數據庫,通過確認該UTXO是否還未被支付來證實交易的有效性。SPV節點則不能驗證UTXO是否還未被支付。相反地,SPV節點會在該交易信息和它所在區塊之間用merkle路徑(見“7.7 Merkle樹”)建立一條鏈接。然后SPV節點一直等待,直到序號從300,001到300,006的六個區塊堆疊在該交易所在的區塊之上,并通過確立交易的深度是在第300,006區塊~第300,001區塊之下來驗證交易的有效性。事實上,如果網絡中的其他節點都接受了第300,000區塊,并通過足夠的工作在該塊之上又生成了六個區塊,根據代理網關協議,就可以證明該交易不是雙重支付。
如果一個交易實際上不存在,SPV節點不會誤認為該交易存在于某區塊中。SPV節點會通過請求merkle路徑證明以及驗證區塊鏈中的工作量證明,來證實交易的存在性。可是,一個交易的存在是可能對SPV節點“隱藏”的。SPV節點毫無疑問可以證實某個交易的存在性,但它不能驗證某個交易(譬如同一個UTXO的雙重支付)不存在,這是因為SPV節點沒有一份關于所有交易的記錄。這個漏洞會被針對SPV節點的拒絕服務攻擊或雙重支付型攻擊所利用。為了防御這些攻擊,SPV節點需要隨機連接到多個節點,以增加與至少一個可靠節點相連接的概率。這種隨機連接的需求意味著SPV節點也容易受到網絡分區攻擊或Sybil攻擊。在后者情況中,SPV節點被連接到虛假節點或虛假網絡中,沒有通向可靠節點或真正的比特幣網絡的連接。
在絕大多數的實際情況中,具有良好連接的SPV節點是足夠安全的,它在資源需求、實用性和安全性之間維持恰當的平衡。當然,如果要保證萬無一失的安全性,最可靠的方法還是運行完整區塊鏈的節點。
完整的區塊鏈節點是通過檢查整個鏈中在它之下的數千個區塊來保證這個UTXO沒有被支付,從而驗證交易。而SPV節點是通過檢查在其上面的區塊將它壓在下面的深度來驗證交易。
SPV節點使用的是一條getheaders消息,而不是getblocks消息來獲得區塊頭。發出響應的對等節點將用一條headers消息發送多達2000個區塊頭。這一過程和全節點獲取所有區塊的過程沒什么區別。SPV節點還在與對等節點的連接上設置了過濾器,用以過濾從對等節點發來的未來區塊和交易數據流。任何目標交易都是通過一條getdata的請求來讀取的。對等節點生成一條包含交易信息的tx消息作為響應。
由于SPV節點需要讀取特定交易從而選擇性地驗證交易,這樣就又產生了隱私風險。與全區塊鏈節點收集每一個區塊內的全部交易所不同的是,SPV節點對特定數據的請求可能無意中透露了錢包里的地址信息。例如,監控網絡的第三方可以跟蹤某個SPV節點上的錢包所請求的全部交易信息,并且利用這些交易信息把比特幣地址和錢包的用戶關聯起來,從而損害了用戶的隱私。
在引入SPV節點/輕量級節點后不久,比特幣開發人員就添加了一個新功能:Bloom過濾器,用以解決SPV節點的隱私風險問題。Bloom過濾器通過一個采用概率而不是固定模式的過濾機制,允許SPV節點只接收交易信息的子集,同時不會精確泄露哪些是它們感興趣的地址。
6.8 Bloom過濾器
Bloom過濾器是一個允許用戶描述特定的關鍵詞組合而不必精確表述的基于概率的過濾方法。它能讓用戶在有效搜索關鍵詞的同時保護他們的隱私。在SPV節點里,這一方法被用來向對等節點發送交易信息查詢請求,同時交易地址不會被暴露。
用我們之前的例子,一位手中沒有地圖的游客需要詢問去特定地方的路線。如果他向陌生人詢問“教堂街23號在哪里”,不經意之間,他就暴露了自己的目的地。Bloom過濾器則會這樣問,附近有帶‘堂’字的街道嗎?”這樣的問法包含了比之前略少的關鍵詞。這位游客可以自己選擇包含信息的多少,比如“以‘堂街’結尾”或者“‘教’字開頭的街道”。如果他問得越少,得到了更多可能的地址,隱私得到了保護,但這些地址里面不乏無關的結果;如果他問得非常具體,他在得到較準確的結果的同時也暴露了自己的隱私。
Bloom過濾器可以讓SPV節點指定交易的搜索模式,該搜索模式可以基于準確性或私密性的考慮被調節。
6.10 交易池
比特幣網絡中幾乎每個節點都會維護一份未確認交易的臨時列表,被稱為內存池或交易池。節點們利用這個池來追蹤記錄那些被網絡所知曉、但還未被區塊鏈所包含的交易。例如,保存用戶錢包的節點會利用這個交易池來記錄那些網絡已經接收但還未被確認的、屬于該用戶錢包的預支付信息。
隨著交易被接收和驗證,它們被添加到交易池并通知到相鄰節點處,從而傳播到網絡中。
有些節點的實現還維護一個單獨的孤立交易池。如果一個交易的輸入與某未知的交易有關,如與缺失的父交易相關,該孤立交易就會被暫時儲存在孤立交易池中直到父交易的信息到達。
當一個交易被添加到交易池中,會同時檢查孤立交易池,看是否有某個孤立交易引用了此交易的輸出(子交易)。任何匹配的孤立交易會被進行驗證。如果驗證有效,它們會從孤立交易池中刪除,并添加到交易池中,使以其父交易開始的鏈變得完整。對新加入交易池的交易來說,它不再是孤立交易。前述過程重復遞歸尋找進一步的后代,直至所有的后代都被找到。通過這一過程,一個父交易的到達把整條鏈中的孤立交易和它們的父級交易重新結合在一起,從而觸發了整條獨立交易鏈進行級聯重構。
交易池和孤立交易池(如有實施)都是存儲在本地內存中,并不是存儲在永久性存儲設備(如硬盤)里。更準確的說,它們是隨網絡傳入的消息動態填充的。節點啟動時,兩個池都是空閑的;隨著網絡中新交易不斷被接收,兩個池逐漸被填充。
有些比特幣客戶端的實現還維護一個UTXO數據庫,也稱UTXO池,是區塊鏈中所有未支付交易輸出的集合。“UTXO池”的名字聽上去與交易池相似,但它代表了不同的數據集。UTXO池不同于交易池和孤立交易池的地方在于,它在初始化時不為空,而是包含了數以百萬計的未支付交易輸出條目,有些條目的歷史甚至可以追溯至2009年。UTXO池可能會被安置在本地內存,或者作為一個包含索引的數據庫表安置在永久性存儲設備中。
交易池和孤立交易池代表的是單個節點的本地視角。取決于節點的啟動時間或重啟時間,不同節點的兩池內容可能有很大差別。相反地,UTXO池代表的是網絡的突顯共識,因此,不同節點間UTXO池的內容差別不大。此外,交易池和孤立交易池只包含未確認交易,而UTXO池之只包含已確認交易。
6.11 警告消息
警告消息并不經常使用,但在大多數節點上都有此功能。警告消息是比特幣的“緊急廣播系統”,比特幣核心開發人員可以借此功能給所有比特幣節點發送緊急文本消息。這一功能是為了讓核心開發團隊將比特幣網絡的嚴重問題通知所有的比特幣用戶,例如一個需要用戶采取措施的的嚴重bug。