漫談分散式系統、拜占庭將軍問題與區塊鏈

張鐵蕾發表於2018-02-02

最近區塊鏈的話題很火。有人想用它改變世界,有人想用它招搖撞騙。

但是我們今天只分析技術。從技術的角度看,區塊鏈是一種與分散式系統有關的技術。它與分散式系統的各個概念之間有什麼聯絡?今天本文就借這個機會,跟大家一起討論一下分散式系統的核心問題和概念。最後,我們將盡量沿著邏輯上前後一貫的思路,討論一下區塊鏈技術。

分散式系統和一致性問題

一個由區塊鏈技術支撐的系統,比如比特幣網路或以太坊,從技術上看,是一個很龐大的分散式系統。因此,我們首先從分散式系統開始說起。

對於技術人員來說,特別是伺服器開發人員,幾乎每個人都經常和分散式系統打交道。當服務的規模越來越大的時候,它必然發展成一個複雜的分散式系統。很典型的,就是各種分散式資料庫,它們通常能將資料以某種方式在多個節點上儲存,在高可用的基礎上保證資料的一致性。

實際上,一致性問題(consensus problem)是分散式系統需要解決的一個核心問題。分散式系統一般是由多個地位相等的節點組成,各個節點之間的互動就好比幾個人聚在一起討論問題。讓我們設想一個更具體的場景,比如三個人討論中午去哪裡吃飯,第一個人說附近剛開了一個火鍋店,聽說味道非常不錯;但第二個人說,不好,吃火鍋花的時間太久了,還是隨便喝點粥算了;而第三個人說,那個粥店我昨天剛去過,太難喝了,還不如去吃麥當勞。結果,三個人僵持不下,始終達不成一致。

有人說,這還不好解決,投票唄。於是三個人投了一輪票,結果每個人仍然堅持自己的提議,問題還是沒有解決。有人又想了個主意,乾脆我們選出一個leader,這個leader說什麼,我們就聽他的,這樣大家就不用爭了。於是,大家開始投票選leader。結果很悲劇,每個人都覺得自己應該做這個leader。三個人終於發現,「選leader」這件事仍然和原來的「去哪裡吃飯」這個問題在本質上是一樣的,同樣難以解決。

這時恐怕有些讀者們心裡在想,這三個人是有毛病吧......就吃個飯這麼點小事,用得著爭成這樣嗎?實際上,在分散式系統中的每個節點之間,如果沒有某種嚴格定義的規則和協議,它們之間的互動就真的有可能像上面說的情形一樣。整個系統達不成一致,就根本沒法工作。

所以,就有聰明人設計出了一致性協議(consensus protocol),像我們常見的比如Paxos、Raft、Zab之類。與前面幾個人商量問題類似,如果翻譯成Paxos的術語,相當於每個節點可以提出自己的提議(稱為proposal,裡面包含提議的具體值),協議的最終目標就是各個節點根據一定的規則達成相同的proposal。但以誰的提議為準呢?我們容易想到的一個規則可能是這樣:哪個節點先提出提議,就以誰的為準,後提出的提議無效。但是,在一個分散式系統中的情況可比幾個人聚在一起討論問題複雜多了,這裡邊還有網路延遲的問題,導致你很難對發生的所有事件進行全域性地排序。舉個簡單的例子,假設節點A和B分別幾乎同時地向節點X和Y發出了自己的proposal,但由於訊息在網路中的延遲情況不同,最後結果是:X先收到了A的proposal,後收到了B的proposal;但是Y正好相反,它先收到了B的proposal,後收到了A的proposal。這樣在X和Y分別看來,誰先誰後就難以達成一致了。

此外,如果考慮到節點當機和訊息丟失的可能性,情況還會更復雜。節點當機可以看成是訊息丟失的特例,相當於發給這個節點的訊息全部丟失了。這在CAP的理論框架下,相當於發生了網路分割(network partitioning),也就是對應CAP中的P。為什麼節點當機和訊息丟失都能歸結到網路分割的情況上去呢?是因為這幾種情況實際上無法區分。比如,有若干個節點聯絡不上了,也就是說,對於其它節點來說,它們傳送給這些節點的訊息收不到沒有回應。真正的原因,可能是網路中間不通了,也可能是那些目的節點當機了,也可能是訊息無限期地被延遲了。總之,就是系統中有些節點聯絡不上了,它們不能再參與決策,但也不代表它們過一段時間不能重新聯絡上。

為了表達上更直觀,下面我們還是假設某些節點當機了。那在這個時候,剩下的節點在缺少了某些節點參與決策的情況下,還能不能對於提議達成一致呢?即使是達成了一致,那麼在那些當機的節點重新恢復過來之後(注意這時候它們對於其它節點之間已經達成一致的提議可能一無所知),它們會不會對於已經達成的一致提議重新提出異議,從而造成混亂?所有這些問題,都是分散式一致性協議需要解決的。

我們這裡沒有足夠的篇幅來詳細討論這些協議具體的實現了,感興趣的讀者可以去查閱相關的論文。實際上,理解問題本身比理解問題的答案要重要的多。總之,我們需要知道的是,我們已經有了一些現成的分散式一致性演算法,它們能解決上面討論的這些問題,保證在一個去中心化的網路中,各個節點之間最終能夠對於提議達成一致。而且,一般來說,只要網路中的大部分節點(或一個quorum)仍然存活(即它們相互間可以收發訊息),這個一致性的提議就可以達成。

拜占庭將軍問題

我們前面討論的一致性協議,有一個重要的前提條件,就是:各個節點都是可以信任的,它們都嚴格遵守同樣的一套規則。這個條件,在一個公司的內部網路中可以認為是基本能滿足的。但如果這個條件不滿足會怎麼樣呢?假設網路中有些節點是惡意的,它們不但不遵守協議,還故意搗亂(比如胡亂傳送訊息),那麼其它正常的節點還能夠順利工作嗎?

在分散式系統理論中,這個問題被抽象成了一個著名的問題——拜占庭將軍問題(Byzantine Generals Problem)。這個問題由大名鼎鼎的Leslie Lamport提出,也就是Paxos的作者。同時,Lamport還是2013年的圖靈獎得主。

這要從一個故事開始說起(當然這個故事是Lamport編出來的)。拜占庭帝國的幾支軍隊攻打到了敵人的城市外面,然後分開駐紮。每一支軍隊由一位拜占庭將軍(Byzantine general)率領。為了制定出一個統一的作戰計劃,每一位將軍需要通過信差(messenger)與其它將軍互通訊息。但是,在拜占庭將軍之間可能出現了叛徒(traitor)。這些叛徒將軍的目的是阻撓其他忠誠的將軍(loyal generals)達成一致的作戰計劃。為了這一目的,他們可能做任何事,比如串通起來,故意傳出虛假訊息,或者不傳出任何訊息。

我們來看一個簡單的例子。假設有5位將軍,他們投票來決定是進攻還是撤退。其中兩位認為應該進攻,還有兩位認為應該撤退,這時候進攻和撤退的票數是2:2打平了。第五位將軍恰好是個叛徒,他告訴前兩位應該進攻,但告訴後兩位應該撤退,結果前兩位將軍最終決定進攻,而後兩位將軍卻決定撤退。沒有達成一致的作戰計劃。

這個問題顯然比我們在前一章討論的可信任環境下的一致性問題要更難。要解決這個問題,我們是希望能找到一個演算法,保證在存在叛徒阻撓的情況下,我們仍然能夠達成如下目標:

  • A. 所有忠誠的將軍都得到了相同(一致)的作戰計劃。比如都決定進攻,或都決定撤退,而不是有些將軍認為應該進攻,其他將軍卻決定撤退。
  • B. 忠誠的將軍不僅得到了相同的作戰計劃,還應該保證得到的作戰計劃是合理的(reasonable)。比如,本來進攻是更有利的作戰計劃,但由於叛徒的阻撓,最終卻制定出了一起撤退的計劃。這樣我們的演算法也算失敗了。

可以看出,上面的目標A,是比較明確的,至少給定一個演算法很容易判定它有沒有達到這個目標。但目標B卻讓人無從下手。一個作戰計劃是不是「合理」的,本來就不好定義。即使沒有叛徒的存在,忠誠的將軍們也未必就一定能制定出合理的計劃。這涉及到科學研究中一個非常重要的問題,如果一個事情不能用一種形式化的方式清晰的定義出來,對於它的研究也就無從談起,這個事情本身也無法上升到科學的層面。Lamport在對拜占庭將軍問題的研究中,一個突出的貢獻就是,把這個看似不太好界定的問題,巧妙地歸約到了一個能用數學語言精確描述的問題上去。下面我們就看一下這個過程是怎麼做的。

首先我們考慮一下將軍們制定作戰計劃的過程(先假設沒有叛徒)。每一位將軍根據自己對戰局的觀察,給出他建議的作戰計劃——是進攻還是撤退。然後,每位將軍把自己的作戰建議通過信差傳達給其他每一位將軍。現在每一位將軍都知道了其他將軍的作戰建議,再加上他自己的作戰建議,他需要根據所有這些資訊得到最終的一個作戰計劃。為了表達上更清晰,我們給每位將軍進行編號,分別是1, 2, ..., n,每位將軍提出的作戰建議記為v(1), v(2), ..., v(n),一共是n個值,這其中有些代表「進攻」,有些代表「撤退」。經過信差傳遞訊息之後,每位將軍都看到了相同的作戰提議v(1), v(2), ..., v(n),當然這其中的一個是當前這位將軍自己提出來的。然後只要每位將軍採用同樣的方法,對所有的v(1), v(2), ..., v(n)這些資訊進行彙總,就能得到同樣的最終作戰計劃。比如,容易想到的一個方法是投票法,即對v(1), v(2), ..., v(n)中不同的作戰計劃進行投票,最後選擇得票最多的作戰計劃。

當然,這樣得到的最終作戰計劃也不能保證就是最好的,但這應該是我們能做到的最好的了。我們現在仍然假設將軍裡沒有叛徒。我們發現,前面提到的目標A和目標B的要求可以適當「降低」一些:我們不再關注將軍們是否能達成最終一致的作戰計劃,並且這個計劃是不是「合理」;我們只關注每個將軍是否收到了完全相同的作戰建議v(1), v(2), ..., v(n)。只要每位將軍收到的這些作戰建議是完全相同的,他們再用同樣的方法進行彙總,就很容易得到最終一致的作戰計劃。至於這個最終的作戰計劃是不是最好的,那就跟很多「人為」的因素有關了,我們不去管它。

現在我們考慮將軍中出現了叛徒。遵循前面的思路,我們仍然希望每位將軍能夠收到完全相同的作戰建議v(1), v(2), ..., v(n)。現在我們仔細審視一下其中的一個值,v(i),在前面的描述中,它表示來自第i個將軍的作戰提議。如果第i個將軍是忠誠的,那麼這個定義沒有什麼問題。但是,如果第i個將軍是叛徒,那麼就有問題了。為什麼呢?因為叛徒可以為所欲為,他為了擾亂整個作戰計劃的制定,完全可能向不同的將軍給出不同的作戰提議。這樣的話,不同的忠誠將軍收到的來自第i個將軍的v(i)可能是不同的值。這樣v(i)這個定義就不對了,它需要改一改。

不管怎麼樣,即使存在叛徒,我們還是希望每位將軍最終是基於完全相同的作戰提議來做彙總,這些作戰提議仍然記為v(1), v(2), ..., v(n)。不過,這裡的v(i)不再表示來自第i個將軍的作戰提議,而是表示經過我們設計的某個一致性演算法處理之後,每位將軍最終看到的第i個提議。這裡需要分兩種情況討論。首先第一種情況,如果第i個將軍是忠誠的,那麼我們自然希望這個v(i)就是第i個將軍傳送出來的作戰提議。換句話說,我們希望經過一致性演算法處理之後,第i個將軍如果是忠誠的,那麼它的提議能夠被如實地傳達給其他將軍,而不會被叛徒的行為所干擾。這是可能制定出「合理」作戰計劃的前提。第二種情況,如果第i個將軍是叛徒,那麼他有可能向不同的將軍傳送不同的提議。這時候我們不能夠只聽他的一面之詞,而是希望經過一致性演算法處理之後,各個將軍之間充分交換意見,然後根據其他各個將軍轉述的資訊,綜合判斷得到一個v(i)。這個v(i)是進攻還是撤退,並不太重要,關鍵是要保證每位將軍得到的v(i)是相同的。只有這樣,各位將軍經過彙總所有的v(1), v(2), ..., v(n)之後才能得到最終的完全一致的作戰計劃。

根據上面的分析,我們發現,在這兩種情況中,我們都只需要關注單個將軍(也就是第i個將軍)所發出的提議如何傳達給其他將軍。重點終於來了!至此,我們就能夠把原來的問題歸約到一個子問題上。這個子問題,才是Leslie Lamport在他的論文中被真正命名為「拜占庭將軍問題(Byzantine Generals Problem)」的那個問題。在這個問題中,我們只關注傳送命令的單個將軍,稱他為主將(commanding general),而其他接受命令的將軍稱為副官(lieutenant)。下面是「拜占庭將軍問題」的精確描述。

一個主將傳送命令給n-1個副官,如何才能確保下面兩個條件:

  • (IC1) 所有忠誠的副官最終都接受相同的命令。
  • (IC2) 如果主將是忠誠的,那麼所有忠誠的副官都接受主將發出的命令。

這其實正好對應了我們前面已經討論過的兩種情況。如果主將是忠誠的,那麼條件IC2保證了命令如實地傳遞,這時候條件IC1自然也滿足了;如果主將是叛徒,那麼條件IC2沒有意義了,而條件IC1保證了,即使叛徒主將對每個副官發出不同的命令,每個副官仍然能最終獲得一致的命令。

這裡有兩個地方可能讓人產生疑惑。

第一,有些人會問了,難道主將還能是叛徒?主將都是叛徒了,還有啥搞頭啊?其實是這樣的,這個「拜占庭將軍問題」只是原問題的一個子問題。當n個將軍通過傳遞訊息來決策作戰計劃的時候,可以分解成n個「拜占庭將軍問題」,即分別以每位將軍作為主將,以其餘n-1位將軍作為副官。如果有一個演算法能夠解決「拜占庭將軍問題」,那麼同時執行n個演算法例項,就能使得每位將軍都獲得完全相同的作戰建議序列,即前面我們提到的v(1), v(2), ..., v(n)。最後,每位將軍將v(1), v(2), ..., v(n)使用相同的方法進行彙總(比如按多數投票),就能得到最終的作戰計劃。

第二,當主將是叛徒的時候,他可以向不同的副官傳送不同的命令,怎麼可能每個副官仍然能最終獲得一致的命令呢?這正是演算法需要解決的。其實這也容易解釋(我們前面也提到過這個思路),由於主將可能向不同的副官傳送不同的命令,所以副官不能直接採用主將發來的命令,而是也要看看其他副官轉述來的主將的命令是什麼。然後,一個副官綜合了由所有副官轉述的命令(再加上主將直接發來的命令)之後,就可能得到比較全面的資訊,從而做出一致的判斷(在實際中是個不斷迭代的過程)。

好了,我們用了這麼多篇幅,終於把「拜占庭將軍問題」本身描述清楚了。這實際上也是最難的部分。我們上一章提到過,理解問題本身比理解問題的答案更重要。只要問題本身分析清楚了,如何設計一個能解決它的演算法就只是細節問題了。我們這裡不深入演算法的細節了,感興趣的讀者可以去查閱下列論文:

我們這裡只提一下論文給出的演算法的結論。

使用不同的訊息模型,「拜占庭將軍問題」有不同的解法。

  • 如果將軍之間使用口頭訊息(oral messages),也就是說,訊息被轉述的時候是可能被篡改的,那麼要對付m個叛徒,需要至少有3m+1個將軍(其中至少2m+1個將軍是忠誠的)。
  • 如果將軍之間使用簽名訊息(signed messages),也就是說,訊息被髮出來之後是無法偽造的,只要被篡改就會被發現,那麼對付m個叛徒,只需要至少m+2個將軍,也就是說至少2個忠誠的將軍(如果只有1個忠誠的將軍,顯然這個問題沒有意義)。這種情況實際相當於對忠誠將軍的數目沒有限制。

容錯性(fault tolerance)

我們前面提到過,以Paxos為代表的分散式一致性協議,是在可信任的環境下執行的。而在「拜占庭將軍問題」中,網路中則存在惡意節點。因此我們很容易產生一個想法:Paxos是不是「拜占庭將軍問題」在叛徒數為零時的一個特例解?

這樣看其實有點問題。在「拜占庭將軍問題」中,除了叛徒,剩下的是忠誠的將軍。「忠誠」這個詞,其實暗含了一個意思:他是能夠正常工作的(即你可以隨時通過訊息跟他進行互動)。為什麼這麼說呢?我們知道,一個叛徒可以做任何事,包括髮送錯誤訊息,也包括不傳送任何訊息。「不傳送任何訊息」,相當於不能正常工作,或者說,發生了某種故障。所以,單純的故障,不僅僅是故意的惡意行為,也應該能歸入叛徒的行為。這在其他將軍看來沒有區別。

按照這種理解,「忠誠」這個詞並不是很恰當。叛徒數為零,相當於網路中每個節點都在正常工作。但是Paxos的設計也是能夠容錯的,就像我們在前面討論的一樣,網路中的少數節點發生故障(比如當機),Paxos仍然能正常工作。可見,Paxos並不能看成是「拜占庭將軍問題」在叛徒數為零時的一個特例解。

那「拜占庭將軍問題」和Paxos這類分散式一致性演算法的關係應該如何看待呢?我們可以從容錯性的強弱程度上來分析。

一般來說,設計一個計算機系統,小到一塊晶片,大到一個分散式網路,都需要考慮一定的容錯性(fault tolerance)。但根據錯誤不同的性質,可以分為兩大類:

  • 拜占庭錯誤(Byzantine fault)。這種錯誤,在不同的觀察者看來,會有前後不一致的表現。
  • 非拜占庭錯誤(non-Byzantine fault)。從字面意思看,是指那些不屬於前一類錯誤的其它錯誤。

這兩類錯誤的含義並沒有字面上那麼好理解。

先說說拜占庭錯誤。在「拜占庭將軍問題」中,叛徒的惡意行為固然是屬於這一類錯誤的。在不同的將軍看來,叛徒可能傳送完全不一致的作戰提議。而在計算機系統中,出現故障的節點或部件也可能表現出前後不一致的行為,雖然這並非惡意,但也屬於這一類錯誤。比如通道不穩定,導致節點傳送給其它節點的訊息發生了隨機錯誤,或者說,訊息損壞了(corrupted)。再比如,在資料庫系統中,commit之後的資料明明已經同步給磁碟了(通過作業系統的fsync),但由於突然斷電等原因,最終資料還是沒有真正落盤成功,甚至出現資料錯亂。

再看一下非拜占庭錯誤。Lamport在他關於Paxos的一篇論文中也使用了non-Byzantine這個詞(見《Paxos Made Simple》)。但是這個詞的命名的確讓人有點不好理解。在分散式系統中,如果節點當機了,或者網路不通了,都會導致某些節點不能工作。其它節點其實沒法區分這兩種情況,在它看來,只是發現某個節點暫時聯絡不上了(即接收訊息超時了)。至於是因為那個節點本身出問題了,還是網路不通了,或者是訊息出現了嚴重的延遲,是無法區分的。而且,過一會之後,節點可能會重新恢復(或是自己恢復了,或經過了人工干預)。換句話說,對於出現這種錯誤的節點,我們只是收不到它的訊息了,而不會收到來自它的錯誤訊息。相反,只要收到了來自它的訊息,那麼訊息本身是「忠實」的。

可見,拜占庭錯誤是更強的一類錯誤。在「拜占庭將軍問題」中,叛徒傳送前後不一致的作戰建議,屬於拜占庭錯誤;而不傳送任何訊息,屬於非拜占庭錯誤。所以,解決「拜占庭將軍問題」的演算法,既能處理拜占庭錯誤,又能處理非拜占庭錯誤。這聽起來稍微有些奇怪,不過這只是命名帶來的問題。

總之,「拜占庭將軍問題」的解法應該是最強的一類分散式一致性演算法,它理論上能夠處理任何錯誤。而Paxos只能處理非拜占庭錯誤。通常把能夠處理拜占庭錯誤的這種容錯性稱為「Byzantine fault tolerance」,簡稱為BFT。

這樣說來,BFT的演算法應該可以解決任何錯誤下的分散式一致性問題,也包括Paxos所解決的問題。那為什麼不統一使用BFT的演算法來解決所有的分散式一致性問題呢?為什麼還需要再費力氣設計Paxos之類的一些演算法呢?我們前面沒有仔細討論解決「拜占庭將軍問題」的演算法,所以這裡也不做仔細的分析了。但容易想象的是,提供BFT這麼強的錯誤容忍性,肯定需要付出很高的代價。比如需要訊息的大量傳遞。而Paxos不需要提供那麼強的容錯性,因此可以比較高效地執行。另外,具體到Lamport在論文中給出的解決「拜占庭將軍問題」的演算法,它還對系統的記時假設(timing assumption)有更強的要求。這也容易理解,既然演算法的容錯性要求這麼高,自然對於執行環境的假設(assumption)也有可能要高一點。由於這個問題是分散式系統中一個挺關鍵的問題,所以我們在這裡單獨拿出來討論一下。在Lamport在論文中,演算法對於系統的假設有這麼一條:

  • The absence of a message can be detected.

這條假設要求,如果某位叛徒將軍沒有傳送任何訊息(當然也可能是訊息丟失了),那麼這件事是可以檢測出來的。顯然,這隻能依賴某種超時機制(time-out),依賴節點之間的時鐘達到一定程度的同步,即時鐘的偏移不能超過一個最大值。這實際上是一種同步模型(synchronous model)。而Paxos的系統假設在這一點上就沒有這麼強,它是基於非同步模型(asynchronous model),對系統時鐘沒有特定的要求。我在之前的另一篇文章《基於Redis的分散式鎖到底安全嗎?》一文中也有提到過這個問題。這有時候會成為分散式演算法帶來一些爭議的根源。

具體來說,根據Paxos的論文所說,Paxos的設計是基於非同步(asynchronous)、非拜占庭(non-Byzantine)的系統模型,即:

  • 節點可以以任意速度執行,可能當機、重啟。但是,演算法執行過程中需要記錄的一些變數,在重啟後應該能夠恢復。
  • 訊息可以延遲任意長時間,可以重複(duplicated),可以丟失(lost),但不能損壞(corrupted)。

上面第一條其實是要求資料在資料庫中持久化,並且要保證在落盤過程中沒有發生拜占庭錯誤(我們前面剛提到過)。但實際中由於突然斷電、磁碟快取等現實問題,拜占庭錯誤是有可能發生的(雖然概率很低),所以這就要求工程上做一些特殊處理。

上面第二條,訊息損壞,屬於拜占庭錯誤。所以Paxos要求不能有訊息損壞發生。這在使用TCP協議進行訊息傳輸的情況下,可以認為是能夠滿足要求的。

綜上分析,解決「拜占庭將軍問題」的演算法,提供了最強的容錯性,即BFT,而Paxos只能容忍非拜占庭錯誤。但是,在只有非拜占庭錯誤出現的前提下,Paxos基於非同步模型,是比同步模型更弱的系統假設,因此演算法更魯棒。當然,Paxos也更高效。

區塊鏈

在現實中,真正需要達到BFT容錯性的系統很少,除非是一些容錯性要求非常高的系統,比如波音飛機上的控制系統,或者SpaceX Dragon太空船這類系統(參見www.weusecoins.com/bitcoin-byz…)。

我們平常能接觸到的BFT的一個典型的例子,就是區塊鏈了。一個區塊鏈網路是一個完全開放的網路,其中的礦工節點(miner)是可以自由加入和自由退出的。這些節點當然有可能是惡意的,所以區塊鏈網路在設計的時候必須要考慮這個問題。這實際上就是典型的「拜占庭將軍問題」。

接下來為了將區塊鏈與「拜占庭將軍問題」之間的聯絡討論得更加清楚,我們先來非常粗略地介紹一下區塊鏈技術。

以比特幣網路為例,它的核心操作就是進行比特幣交易,即某個比特幣的擁有者將自己一定數量的比特幣轉移給其他人。首先,比特幣的擁有者要發起交易(transaction),他需要先用自己的私鑰對交易進行簽名,然後將交易請求發給礦工節點。礦工將收到的所有交易打包到一個區塊(block)當中,並通過一系列複雜度很高的運算找到一個nonce值,保證對於它和區塊內其它資訊進行hash計算後的結果能夠符合預定的要求。這一步對於整個區塊鏈網路至關重要,被稱為工作量證明(Proof of Work)。然後礦工把該區塊在全網釋出,由其它礦工來驗證這個區塊。這個驗證既包括對交易簽名進行驗證(使用比特幣擁有者的公鑰),也包括對工作量證明的有效性進行驗證。如果驗證通過,就把這個區塊掛在當前最長的區塊鏈上。

如果兩個礦工幾乎同時完成了區塊的打包和工作量證明,它們可能都會將區塊進行釋出,這時區塊鏈就會分叉(fork)。但礦工會不停地產生新區塊,並將新區塊掛在當前最長的區塊鏈上,所以最終哪個分叉變得更長,哪個分叉就會被多數礦工節點承認。這麼看來,區塊鏈其實不是一個鏈,而是一棵樹。我們知道,在樹這種資料結構中,從根節點到葉子節點只有唯一的一條路徑。因此,當前有效的區塊鏈其實是這棵樹中從根節點到葉子節點最長的那條路徑。只要一個區塊在最長的鏈上,那麼它就是有效的,它裡面包含的所有交易就被固化下來了(被多數節點承認)。

我們只是非常粗略地介紹了一下區塊鏈的工作原理。如果想了解細節,建議研究一下以太坊的官方wiki,地址是:

github.com/ethereum/wi…

下面我們開始討論區塊鏈技術與「拜占庭將軍問題」的關聯。

前面我們討論「拜占庭將軍問題」的時候,得到過以下結論:

  • 如果使用口頭訊息,那麼至少需要多於2/3的將軍是忠誠的。
  • 如果使用簽名訊息,那麼對忠誠將軍的數量是沒有要求的。

根據前面的介紹,我們在區塊鏈中使用的訊息應該屬於簽名訊息。具體體現在:每一個區塊中的交易都進行了簽名,保證無法被篡改,也能保證這個訊息只能是由最初的發起者發出的。那麼,這屬於上述第二種情況,難道說區塊鏈網路中忠誠節點的數目沒有要求?顯然不是這樣。比如在比特幣網路中,要求惡意節點不能掌握多於50%的算力。為什麼兩者之間似乎不一致呢?這是因為,「拜占庭將軍問題」只是關注一個子問題,它關注的是其中一個將軍(稱為主將)向其他所有將軍(稱為副官)傳送命令的情況。而最終對所有命令進行彙總則要求所有忠誠的將軍達成共識。如果忠誠的將軍數目太少,不管最終確定的作戰計劃是什麼,還是會失敗,因為叛徒可能不執行這個作戰計劃。這類似於比特幣網路中的情況,其中對於最長鏈的選擇過程,就相當於將軍們對所有命令進行彙總的操作(按多數投票)。

在「拜占庭將軍問題」中,一個叛徒可能向不同的將軍傳送不一致的命令。如果演算法設計得不好,就可能造成最終無法達成一致。在區塊鏈網路中,類似的行為將會成本很高。這是由於礦工節點發布區塊的訊息必須經過工作量證明,它如果釋出不一致的區塊,每個區塊都需要工作量證明,這將耗費它大量的算力。另外,這樣做也沒有動機,它只會產生更多分叉,不會產生最長鏈。

在「拜占庭將軍問題」的框架下,如何看待工作量證明呢?它其實相當於提高了做叛徒的成本,從而極大降低了叛徒超過半數的可能性。這裡可以做一個對比,假設歷史上存在真實的拜占庭將軍問題,那麼可以想象,敵軍的間諜打入拜占庭將軍這個群體中的成本應該是很高的。所以,可以認為將軍中的叛徒不至於太多。但對應到計算機網路中,如果沒有類似工作量證明的機制,那麼成為叛徒礦工的成本就是非常低的。這就很有可能使得叛徒比忠誠的礦工還要更多。

當然,從經濟學的角度看,在需要工作量證明的前提下,成為叛徒礦工也是不明智的。因為它既然擁有比較強的算力,還不如按照合理的方式通過挖礦賺取收益更為穩妥。不過這是技術之外的因素了。

除了工作量證明這種機制(Proof of Work)之外,還有一種被稱為Proof of Stake的機制。雖然有人質疑這種機制存在缺點(比如nothing at stake),但站在「拜占庭將軍問題」的角度,它也是相當於提高了做叛徒的成本。這就好比一個間諜要混入董事會,成本肯定是比較高的,因為他需要首先持有大量股票。

區塊鏈到底是什麼?有人說是個無法篡改的超級賬本,也有人說是個去中心化的交易系統,還有人說它是構建數字貨幣的底層工具。但是,從技術的角度來說,它首先是個解決了拜占庭將軍問題的分散式網路,在完全開放的環境中,實現了資料的一致性和安全性。而其它的屬性,都附著於這一技術本質之上。


在今天這篇文章中,我們從分散式系統的一致性問題開始談起,直到「拜占庭將軍問題」,最後又跟區塊鏈產生了關聯。這幾部分逐步深入,雖是「漫談」,邏輯上卻是前後貫穿的。

最後,區塊鏈,是不是一項偉大的革新?是。特別是比特幣系統,它是分散式系統技術與金融系統業務相結合造就的一次成功的創舉。

區塊鏈技術到底會不會顛覆未來呢?我們現在只能說:有可能。

(完)

其它精選文章

漫談分散式系統、拜占庭將軍問題與區塊鏈

相關文章