到這裡,我們已經討論了參與者可以發佈交易,並將交易納入區塊鏈,這一切似乎很神奇。事實上,上述整個過程都是通過比特幣網絡完成的。比特幣網絡是一個點對點的網絡,沿用了很多已有的點對點網絡的理念。在比特幣網絡裡,所有的節點都是平等的。沒有等級,也沒有特殊的節點,或所謂的主節點。它運行在TCP網絡上,有一個隨意的拓撲結構,每個節點和其他的隨機節點相連。新的節點也可以隨時加入。可以試著現在就下載比特幣節點軟件,把你的個人電腦註冊為一個節點,這個節點的權限和比特幣網絡裡所有其他節點都是一樣的。
由於隨時有新的節點進入,也有舊的節點離開,所以比特幣網絡事實上一直在變化。並沒有強制的規定節點何時明確地離開網絡,只要一個節點有3個小時沒有音訊,就會慢慢地被其他節點忘記。通過這個方式,網絡非常緩和地處理節點下線問題。
上文提到,每個節點和其他隨機節點相連,網絡中並不存在一個確定的地理學意義上的拓撲結構。那麼一個節點是如何加入網絡的呢?當你啟動一個新節點的時候,先向一個你知道的節點發送一個簡單的消息。這個節點就是你的種子節點,當然,有多種不同的方法可以查找種子節點。然後你就會問你的種子節點是不是還知道其他什麼節點?在鏈接到一個新的節點後,你可以重複這個過程許多次,最後你可以選擇和哪些節點相連,這時,你就成為比特幣網絡裡一個完全合格的節點了。這些步驟裡有很多隨機性,理想的情況就是你能和一些隨機組的節點相連。為了加入網絡成為網絡節點,你只需知道一開始怎麼和其中一個節點鏈接就行了。
那加入網絡到底有什麼好處?當然是為了維護區塊鏈。當我們發起一個交易的時候,我們想讓整個網絡都知道。這是通過一個「泛洪」(flooding)的算法完成的〔有時候我們稱之為「八卦」(gossip)協議〕。如果愛麗絲要轉賬給鮑勃,她的客戶端發起一個交易,然後把這筆交易告知所有和她的客戶端節點相鏈接的其他節點,這些節點會進行一系列核驗,決定是否接受並轉播這筆交易。如果核驗通過,這些節點會將這筆交易信息傳播給與其相連的其他節點。當節點接收到一個交易信息後,會把交易放入一個交易池,但需要注意的是,交易池裡的交易還沒有被打包進區塊鏈。如果節點接收到的交易在交易池裡已經存在,就不會再次把它傳播出去。這樣,就確保了泛洪協議會自動終結,而不是讓一個交易在網絡一再被傳播永不停止。由於每個交易都有一個獨一無二的哈希值,所以節點可以非常方便地查詢某個交易是否在自己的交易池裡。
節點接收到一個新交易信息時,如何核驗呢?這裡有四個關卡:第一個也是最重要的一個是交易驗證,也就是驗證交易在當前的區塊鏈中是有效的,節點會針對每個前序交易的輸出運行核驗腳本,確保腳本的返回值都為真;第二,檢查是否有雙重支付;第三,如前文所述,節點會檢查這筆交易信息是不是已經被本節點接收過;第四,節點只會接收和傳遞在白名單上的標準腳本。
上述所有檢查都是合理檢查,所有節點很好地執行這些檢查能夠使網絡健康、穩定地運行,但實際上並沒有規則強制節點執行這些檢查。雖然如此,每個節點還是有必要進行檢查的——因為比特幣網絡是一個點對點的對等網絡,任何人都可以隨時加入,總有一些節點會發出雙重支付,或者非標準腳本的交易,甚至徹底就是非法交易。
由於網絡傳遞有延遲,不同的節點可能會有不同的交易池。當有雙重支付攻擊的時候,這個現象會變得十分有意思。假設愛麗絲想把同一個比特幣支付給鮑勃與查理,於是,愛麗絲幾乎同時發出兩筆交易。有些節點先聽到愛麗絲→鮑勃交易,有些則先聽到愛麗絲→查理交易。當一個節點接收到了這兩個交易當中任何一個,它就會把接收到的交易放入交易池中,之後,它聽到了另一個交易,看上去像是雙重支付交易,這個節點就會把它丟棄掉不再向外傳播。結果就是眾多的節點會對「哪一個交易應該被納入區塊鏈」產生分歧。這種情況被稱為競態條件[1](race condition)。
好在對於比特幣來說,這完全不是問題:打包下一個區塊的礦工會打破這個僵局,他會決定哪個交易會最終打包進這個區塊。如果愛麗絲→鮑勃的交易進入區塊,那些聽到愛麗絲→查理的節點會把愛麗絲→查理的交易從交易池裡剔除,因為那是一個雙重支付;而那些聽到愛麗絲→鮑勃的節點也會把這個交易剔除出去,因為這筆交易已經被納入區塊鏈。因此,一旦這個區塊被傳播以後,就不再有前面說的分歧了。
由於每個節點默認保留最早接收到的交易,所以節點在網絡上的位置就很重要。如果兩個矛盾的交易或區塊在網絡上兩個不同地方被發起,它們會同時向整個網絡廣播,節點先接收到哪個交易取決於它在網絡的位置。
當然,這基於一個假設:不管接收到什麼信息,每個節點均保留最早接收到的交易。但是比特幣網絡是一個對等的網絡,節點並不被強制要求這麼做,任何節點都有權按照其他邏輯行事,並按照所選的邏輯決定到底保留哪個交易、轉播哪些交易,我們會在第5章的礦工獎勵部分討論這個問題。
零驗證交易和費用替代策略(replace-by-fee)
在第2章我們討論了零驗證交易,即一旦交易在網絡中廣播,接收方就立即接受交易。零驗證交易不是用來防止重複支付的,但由於礦工的缺省行為是把先接收到的交易放入交易池,這樣,在零驗證交易裡就很難實現重複支付,同時,由於零驗證交易非常方便,因此變得越來越普及。
自從2013年,礦工的缺省行為變成了「費用替代策略」, 即節點在遇到有衝突的交易時,會把交易手續費更高的交易放進自己的交易池,把手續費更低的替換出去。站在礦工的角度,由於收益更高,因此也是理性的選擇——至少在短期看是這樣。但是這種費用替代策略卻使多重支付攻擊變得更容易了。
因此,費用替代策略受到了不少爭議,這些爭議一方面從技術層面討論在費用替代策略中是否可以真正阻止多重支付;另一方面從哲學層面討論比特幣是不是應該要盡可能支持零驗證,或直接放棄費用替代策略。我們這裡就不再贅述這些討論了很久的爭議了,但最近比特幣核心代碼倒選用了「有選擇權的」(opt-in)費用替代策略的做法,也就是交易可以標記自己是否適用費用替代策略。
上面說的是交易的傳播。至於區塊的傳播,即礦工挖到一個礦(打包一個區塊),然後將區塊加入區塊鏈,這個過程與新交易的傳播過程類似,也受同樣競態條件的限制。如果兩個有效的區塊同時被挖到(也就是有兩個礦工同時獲得了記賬權力時),只有其中一個區塊可以進入長期共識鏈,哪個區塊被最終納入長期共識鏈取決於其他節點選擇在哪個區塊上擴展區塊鏈,未被納入的一個即被丟棄。
核驗一個區塊要比核驗一個交易複雜得多。除了確認區塊頭部,確定裡面的哈希值是在可以接受的範圍內,節點還必須確認區塊裡的每個交易。最後,一個節點往外傳播的區塊必須是最長的一條區塊鏈上新加入的區塊(當然,「最長的區塊鏈」取決於節點對區塊鏈當前狀態的認識)。只有這樣才可以防止區塊鏈分叉。但就像傳播交易時一樣,節點同樣可以執行它自己的邏輯:它可以選擇傳遞無效的區塊,也可以選擇傳遞在共識鏈上更早加入的區塊而不是最新加入的區塊。這樣就會造成一個分叉,不過這種情況是協議可以承受的。
泛洪算法(flooding algorithm)的延遲情況到底怎樣呢?我們一起看一下圖3.9,這張圖展示了區塊被網絡中不同數量的節點接收所花費的時間(秒)。三條線分別代表區塊被網絡中25%、50%、75%的節點接收到所需要的時間。可以看到,由於網絡帶寬的限制,比較大的區塊需要30秒左右才能傳播到大部分的節點。所以這個協議不是很有效率。在互聯網上30秒是比較長的時間了,在比特幣的設計裡,簡便是第一位的(簡單的網絡、節點可隨時加入或退出),而效率是第二位的,所以在比特幣網絡裡,一個區塊可能需要經過很多節點才到達最遠的節點。如果網絡採取自上而下的設計,那我們就需要使任何兩個節點的距離都很短。
網絡大小
比特幣網絡大小很難測量,因為它隨時都在變化,而且沒有一個中央權威機構。有些人通過研究給了一些估計:往高說,每個月可能有100萬個IP地址成為比特幣網絡的節點(也可能是臨時成為節點)。往低說,大約只有5 000~10 000節點永遠在線並處理交易。這個數字有點出乎意料得小,但是截至本書完成時,並沒有證據表明永遠在線的節點數量在升高或降低。
圖3.9 區塊傳播時間
註:展示了區塊被網絡中不同數量(百分比)的節點接受所花費的時間。
資料來源: Yonatan Sompolinsky和Aviv Zohar, 加快比特幣交易的傳播速度 (Accelerating Bitcoin\'s Transaction Processing,2014)。可從下述網址獲得https://eprint.iacr.org/2013/881。數據由Yonatan Sompolinsky和Aviv Zohar授權使用
存儲空間需求
完全有效的節點必須永久在線,這樣才能接收到所有的交易數據。一個節點離線時間越久,當它重新連接到網絡的時候,就需要越多時間來更新所有交易。這些節點還需要把完整的共識區塊鏈都存儲下來,也需要有好的網絡連接,確保可以接收到所有交易並將其轉播給其他節點。目前的存儲空間大約要幾十個GB(見圖3.10),一台台式機就能滿足要求。
最後,完全有效節點必須維護在交易中產生的(交易的輸出)、未被消費掉的比特幣的完整列表,這個列表最好放在內存而非硬盤裡,這樣,在接收到一個交易信息的時候,節點才能快速查看、運行腳本,驗證簽名是否有效,然後把交易放入交易池。到2014年年中,大約有4 400萬的交易被納入區塊鏈,其中有1 200萬個交易產生的比特幣沒有被使用。還好,這個數據不大,可以很容易地放進1G內存裡。
圖3.10 區塊鏈的大小
註:全節點必須保持整個區塊鏈,在2015年年底,區塊鏈大小在50GB以上。
輕量節點
除了完全有效節點之外,還有一種輕量節點(nightweight nodes),或者稱為輕客戶端,也叫簡單付款驗證(Simple Payment Verification,簡稱SPV)客戶端。事實上,在比特幣系統裡的大部分節點都是輕量節點。這些節點不會存儲整個比特幣區塊鏈,它們只存儲它們所關心的、需要進行核驗的部分交易。如果你使用一個錢包軟件,那裡面就會有一個SPV節點,這個節點只會下載向你的賬戶付款的交易及區塊頭部。
一個SPV節點的安全等級遠不如全節點。它可以核驗那些很難被挖到的區塊——因為它有區塊頭部數據,但它不能核驗一個區塊裡所有交易記錄的有效性——因為它沒有所有的交易歷史記錄,也沒有那些未被消費的比特幣的列表。SPV節點只驗證那些和它們相關的交易,所以它們必須依賴那些全節點去驗證網絡上的其他所有交易。這雖然是一種安全性上的妥協,卻不是個壞主意:輕量節點依賴全節點去處理那些比較難的工作,但當某個區塊由於某些原因未被礦工挖出來時(挖礦成本巨大),這些輕量節點也會做一些核驗來確保這個區塊不會被拒絕。
作為一個SPV節點可以節省很多資源。區塊頭部的大小只是整個區塊鏈的千分之一。所以輕量節點不需要幾十G的存儲空間,只需要幾十MB即可,即使一部智能手機也能成為比特幣網絡的輕量節點。
比特幣是一個開源協議,比特幣網絡一定是由實現方式各不相同的軟件系統在無縫交互。這樣,即使有些軟件系統有缺陷,也不至於使整個比特幣網絡癱瘓。比較好的現象是,人們用不同的語言不斷地重新實現協議,有些人用C++、有些人用Go語言,還有不少人用其他語言。不好的現象是,絕大部分的節點都會調用比特幣官方客戶端的資源庫(bitcoind library),這個庫是比特幣核心代碼開發者們用C++開發的庫,而且有些節點用的是過時的版本。所以,即使在同一時間,大家運行的客戶端都略有不同。
[1] 競態條件也可理解為紊亂情況。——譯者注