區塊鏈核心技術:拜占庭共識算法之PBFT

PBFT是Practical Byzantine Fault Tolerance的縮寫,意爲實用拜占庭容錯算法。該算法是Miguel Castro (卡斯特羅)和Barbara Liskov(利斯科夫)在1999年提出來的,解決了原始拜占庭容錯算法效率不高的問題,將算法複雜度由指數級下降到多項式級,使得拜占庭容錯算法在實際系統應用中變得可行。該論文發表在1999年的操做系統設計與實現國際會議上(OSDI99)。沒錯,這個Loskov就是提出著名的里氏替換原則(LSP)的人,2008年圖靈獎得主。算法

摘要部分

OSDI99這篇論文描述了一種副本複製(replication)算法解決拜占庭容錯問題。做者認爲拜占庭容錯算法將會變得更加劇要,由於惡意攻擊和軟件錯誤的發生將會愈來愈多,而且致使失效的節點產生任意行爲。(拜占庭節點的任意行爲有可能誤導其餘副本節點產生更大的危害,而不只僅是宕機失去響應。)而早期的拜占庭容錯算法或者基於同步系統的假設,或者因爲性能過低而不能在實際系統中運做。這篇論文中描述的算法是實用的,由於該算法能夠工做在異步環境中,而且經過優化在早期算法的基礎上把響應性能提高了一個數量級以上。做者使用這個算法實現了拜占庭容錯的網絡文件系統(NFS),性能測試證實了該系統僅比無副本複製的標準NFS慢了3%。緩存

1.概要介紹

第一段大片廢話就是說明拜占庭算法愈來愈重要了,而後說這篇論文提出解決拜占庭容錯的狀態機副本複製(state machine replication)算法。這個算法在保證活性和安全性(liveness & safety)的前提下提供了(n-1)/3的容錯性。從Lamport教授在1982年提出拜占庭問題開始,已經有一大堆算法去解決拜占庭容錯了。可是一句話歸納:這些算法都是狗屎!PBFT算法跟這些妖豔賤貨徹底不一樣,在只讀操做中只使用1次消息往返(message round trip),在只寫操做中只使用2次消息往返,而且在正常操做中使用了消息驗證編碼(Message Authentication Code,簡稱MAC),而形成妖豔賤貨性能低下的公鑰加密(public-key cryptography)只在發生失效的狀況下使用。做者不只提出算法,並且使用這個算法實現了一個牛逼的系統(拜占庭容錯的NFS),反正性能槓槓的。
做者先炫耀一下這邊論文的貢獻亮瞎大家的狗眼:安全

1)首次提出在異步網絡環境下使用狀態機副本複製協議
2)使用多種優化使性能顯著提高
3)實現了一種拜占庭容錯的分佈式文件系統
4)爲副本複製的性能損耗提供試驗數據支持網絡

2.系統模型

這部分主要對節點行爲和網絡環境進行劇情設定,而後賦予了消息的加密屬性,最後對大魔王的能力進行設定。異步

系統假設爲異步分佈式的,經過網絡傳輸的消息可能丟失、延遲、重複或者亂序。做者假設節點的失效必須是獨立發生的,也就是說代碼、操做系統和管理員密碼這些東西在各個節點上是不同的。(那麼若是節點失效不獨立發生,PBFT算法就失效了嗎?)分佈式

做者使用了加密技術來防止欺騙攻擊和重播攻擊,以及檢測被破壞的消息。消息包含了公鑰簽名(其實就是RSA算法)、消息驗證編碼(MAC)和無碰撞哈希函數生成的消息摘要(message digest)。使用m表示消息,mi表示由節點i簽名的消息,D(m)表示消息m的摘要。按照慣例,只對消息的摘要簽名,而且附在消息文本的後面。而且假設全部的節點都知道其餘節點的公鑰以進行簽名驗證函數

系統容許大魔王能夠操縱多個失效節點、延遲通信、甚至延遲正確節點來毀滅世界。可是做者限定大魔王不能無限期地延遲正確的節點,而且大魔王算力有限不能破解加密算法。例如,大魔王不能僞造正確節點的有效簽名,不能從摘要數據反向計算出消息內容,或者找到兩個有一樣摘要的消息。性能

3.服務屬性

這部分描述了副本複製服務的特性測試

論文算法實現的是一個具備肯定性的副本複製服務,這個服務包括了一個狀態(state)和多個操做(operations)。這些操做不只可以進行簡單讀寫,並且可以基於狀態和操做參數進行任意肯定性的計算。客戶端向副本複製服務發起請求來執行操做,而且阻塞以等待回覆。副本複製服務由n個節點組成。優化

針對安全性

算法在失效節點數量不超過(n-1)/3的狀況下同時保證安全性和活性(safety & liveness)。安全性是指副本複製服務知足線性一致性(linearizability),就像中心化系統同樣原子化執行操做。安全性要求失效副本的數量不超過上限,可是對客戶端失效的數量和是否與副本串謀不作限制。系統經過訪問控制來限制失效客戶端可能形成的破壞,審覈客戶端並阻止客戶端發起無權執行的操做。同時,服務能夠提供操做來改變一個客戶端的訪問權限。由於算法保證了權限撤銷操做能夠被全部客戶端觀察到,這種方法能夠提供強大的機制從失效的客戶端攻擊中恢復。

針對活性

算法不依賴同步提供安全性,所以必須依靠同步提供活性。不然,這個算法就能夠被用來在異步系統中實現共識,而這是不可能的(由Fischer1985的論文證實)。本文的算法保證活性,即全部客戶端最終都會收到針對他們請求的回覆,只要失效副本的數量不超過(n-1)/3,而且延遲delay(t)不會無限增加。這個delay(t)表示t時刻發出的消息到它被目標最終接收的時間間隔,假設發送者持續重傳直到消息被接收。這時一個至關弱的同步假設,由於在真實系統中網絡失效最終都會被修復。可是這就規避了Fischer1985提出的異步系統沒法達成共識的問題。

下面這段話是關鍵

本文的算法彈性是達到最優的:當存在f個失效節點時必須保證存在至少3f+1
個副本數量,這樣才能保證在異步系統中提供安全性和活性。這麼多數量的副本是須要的,由於在同n-f個節點通信後系統必須作出正確判斷,因爲f個副本有可能失效而不發回響應。可是,有可能f個沒有失效的副本不發回響應(是由於網絡延遲嗎?),所以f個發回響應的副本有多是失效的。儘管如此,系統仍舊須要足夠數量非失效節點的響應,而且這些非失效節點的響應數量必須超過失效節點的響應數量,即n-2f>f,所以獲得n>3f

算法不能解決信息保密的問題,失效的副本有可能將信息泄露給攻擊者。在通常狀況下不可能提供信息保密,由於服務操做須要使用參數和服務狀態處理任意的計算,全部的副本都須要這些信息來有效執行操做。固然,仍是有可能在存在惡意副本的狀況下經過祕密分享模式(secret sharing scheme)來實現私密性,由於參數和部分狀態對服務操做來講是不可見的。

4.算法

PBFT是一種狀態機副本複製算法,即服務做爲狀態機進行建模,狀態機在分佈式系統的不一樣節點進行副本複製。每一個狀態機的副本都保存了服務的狀態,同時也實現了服務的操做。將全部的副本組成的集合使用大寫字母R表示,使用0到|R|-1的整數表示每個副本。爲了描述方便,假設|R|=3f+1,這裏f是有可能失效的副本的最大個數。儘管能夠存在多於3f+1個副本,可是額外的副本除了下降性能以外不能提升可靠性。

PBFT的劇情緩緩展開,首先介紹舞臺(view)、演員(replica)和角色(primary、backups)

全部的副本在一個被稱爲視圖(View)的輪換過程(succession of configuration)中運做。在某個視圖中,一個副本做爲主節點(primary),其餘的副本做爲備份(backups)。視圖是連續編號的整數。主節點由公式p = v mod |R|計算獲得,這裏v是視圖編號,p是副本編號,|R|是副本集合的個數。當主節點失效的時候就須要啓動視圖更換(view change)過程。Viewstamped Replication算法和Paxos算法就是使用相似方法解決良性容錯的。

PBFT算法的狗血劇情以下:
1.客戶端向主節點發送請求調用服務操做
2.主節點經過廣播將請求發送給其餘副本
3.全部副本都執行請求並將結果發回客戶端
4.客戶端須要等待f+1個不一樣副本節點發回相同的結果,做爲整個操做的最終結果。

同全部的狀態機副本複製技術同樣,PBFT對每一個副本節點提出了兩個限定條件:(1)全部節點必須是肯定性的。也就是說,在給定狀態和參數相同的狀況下,操做執行的結果必須相同;(2)全部節點必須從相同的狀態開始執行。在這兩個限定條件下,即便失效的副本節點存在,PBFT算法對全部非失效副本節點的請求執行總順序達成一致,從而保證安全性。

接下去描述簡化版本的PBFT算法,忽略磁盤空間不足和消息重傳等細節內容。而且,本文假設消息驗證過程是經過數字簽名方法實現的,而不是更加高效的基於消息驗證編碼(MAC)的方法。

4.1客戶端

客戶端c向主節點發送<REQUEST,o,t,c>請求執行狀態機操做o,這裏時間戳t用來保證客戶端請求只會執行一次。客戶端c發出請求的時間戳是全序排列的,後續發出的請求比早先發出的請求擁有更高的時間戳。例如,請求發起時的本地時鐘值能夠做爲時間戳。

每一個由副本節點發給客戶端的消息都包含了當前的視圖編號,使得客戶端可以跟蹤視圖編號,從而進一步推算出當前主節點的編號。客戶端經過點對點消息向它本身認爲的主節點發送請求,而後主節點自動將該請求向全部備份節點進行廣播。

副本發給客戶端的響應爲<REPLY,v,t,c,i,r>,v是視圖編號,t是時間戳,i是副本的編號,r是請求執行的結果。

客戶端等待f+1個從不一樣副本獲得的一樣響應,一樣響應須要保證簽名正確,而且具備一樣的時間戳t和執行結果r。這樣客戶端才能把r做爲正確的執行結果,由於失效的副本節點不超過f個,因此f+1個副本的一致響應一定可以保證結果是正確有效的。

若是客戶端沒有在有限時間內收到回覆,請求將向全部副本節點進行廣播。若是請求已經在副本節點處理過了,副本就向客戶端重發一遍執行結果。若是請求沒有在副本節點處理過,該副本節點將把請求轉發給主節點。若是主節點沒有將該請求進行廣播,那麼就有認爲主節點失效,若是有足夠多的副本節點認爲主節點失效,則會觸發一次視圖變動。

本文假設客戶端會等待上一個請求完成纔會發起下一個請求,可是隻要可以保證請求順序,能夠容許請求是異步的。

4.2 PBFT算法主線流程(正常狀況)

世界格局

每一個副本節點的狀態都包含了服務的總體狀態,副本節點上的消息日誌(message log)包含了該副本節點接受(accepted)的消息,而且使用一個整數表示副本節點的當前視圖編號。

事件的導火索

當主節點p收到客戶端的請求m,主節點將該請求向全部副本節點進行廣播,由此一場轟轟烈烈的三階段協議(three-phase protocol)拉開了序幕。在這裏,至於什麼消息過多須要緩存的狀況咱們就無論了,這不是重點。

三個階段的任務

咱們重點討論預準備(pre-prepare)、準備(prepare)和確認(commit)這三個歷史性階段。預準備和準備兩個階段用來確保同一個視圖中請求發送的時序性(即便對請求進行排序的主節點失效了),準備和確認兩個階段用來確保在不一樣的視圖之間的確認請求是嚴格排序的。

預準備階段

在預準備階段,主節點分配一個序列號n給收到的請求,而後向全部備份節點羣發預準備消息,預準備消息的格式爲<<PRE-PREPARE,v,n,d>,m>,這裏v是視圖編號,m是客戶端發送的請求消息,d是請求消息m的摘要。

請求自己是不包含在預準備的消息裏面的,這樣就能使預準備消息足夠小,由於預準備消息的目的是做爲一種證實,肯定該請求是在視圖v中被賦予了序號n,從而在視圖變動的過程當中能夠追索。另一個層面,將「請求排序協議」和「請求傳輸協議」進行解耦,有利於對消息傳輸的效率進行深度優化。

備份節點對預準備消息的態度

只有知足如下條件,各個備份節點纔會接受一個預準備消息:

  1. 請求和預準備消息的簽名正確,而且d與m的摘要一致。
  2. 當前視圖編號是v。
  3. 該備份節點從未在視圖v中接受過序號爲n可是摘要d不一樣的消息m。(許仙在這輩子從未見過名字叫白素貞的美貌女子)
  4. 預準備消息的序號n必須在水線(watermark)上下限h和H之間。

水線存在的意義在於防止一個失效節點使用一個很大的序號消耗序號空間。

進入準備階段

若是備份節點i接受了預準備消息<<PRE-PREPARE,v,n,d>,m>,則進入準備階段。在準備階段的同時,該節點向全部副本節點發送準備消息<PREPARE,v,n,d,i>而且將預準備消息和準備消息寫入本身的消息日誌。若是看預準備消息不順眼,就什麼都不作。

接受準備消息須要知足的條件

包括主節點在內的全部副本節點在收到準備消息以後,對消息的簽名是否正確,視圖編號是否一致,以及消息序號是否知足水線限制這三個條件進行驗證,若是驗證經過則把這個準備消息寫入消息日誌中。

準備階段完成的標誌

咱們定義準備階段完成的標誌爲副本節點i將(m,v,n,i)記入其消息日誌,其中m是請求內容,預準備消息m在視圖v中的編號n,以及2f個從不一樣副本節點收到的與預準備消息一致的準備消息。每一個副本節點驗證預準備和準備消息的一致性主要檢查:視圖編號v、消息序號n和摘要d。

預準備階段和準備階段確保全部正常節點對同一個視圖中的請求序號達成一致。接下去是對這個結論的形式化證實:若是prepared(m,v,n,i)爲真,則prepared(m',v,n,j)必不成立,這就意味着至少f+1個正常節點在視圖v的預準備或者準備階段發送了序號爲n的消息m。

進入確認階段

當(m,v,n,i)條件爲真的時候,副本i將<COMMIT,v,n,D(m),i>向其餘副本節點廣播,因而就進入了確認階段。每一個副本接受確認消息的條件是:1)簽名正確;2)消息的視圖編號與節點的當前視圖編號一致;3)消息的序號n知足水線條件,在h和H之間。一旦確認消息的接受條件知足了,則該副本節點將確認消息寫入消息日誌中。(補充:須要將針對某個請求的全部接受的消息寫入日誌,這個日誌能夠是在內存中的)。

接受確認消息須要知足的條件

咱們定義確認完成committed(m,v,n)爲真得條件爲:任意f+1個正常副本節點集合中的全部副本i其prepared(m,v,n,i)爲真;本地確認完成committed-local(m,v,n,i)爲真的條件爲:prepared(m,v,n,i)爲真,而且i已經接受了2f+1個確認(包括自身在內)與預準備消息一致。確認與預準備消息一致的條件是具備相同的視圖編號、消息序號和消息摘要。

確認被接受的形式化描述

確認階段保證瞭如下這個不變式(invariant):對某個正常節點i來講,若是committed-local(m,v,n,i)爲真則committed(m,v,n)也爲真。這個不變式和視圖變動協議保證了全部正常節點對本地確認的請求的序號達成一致,即便這些請求在每一個節點的確認處於不一樣的視圖。更進一步地講,這個不變式保證了任何正常節點的本地確認最終會確認f+1個更多的正常副本。

故事的終結

每一個副本節點i在committed-local(m,v,n,i)爲真以後執行m的請求,而且i的狀態反映了全部編號小於n的請求依次順序執行。這就確保了全部正常節點以一樣的順序執行全部請求,這樣就保證了算法的正確性(safety)。在完成請求的操做以後,每一個副本節點都向客戶端發送回覆。副本節點會把時間戳比已回覆時間戳更小的請求丟棄,以保證請求只會被執行一次。

咱們不依賴於消息的順序傳遞,所以某個副本節點可能亂序確認請求。由於每一個副本節點在請求執行以前已經將預準備、準備和確認這三個消息記錄到了日誌中,這樣亂序就不成問題了。(爲何?)

下圖展現了在沒有發生主節點失效的狀況下算法的正常執行流程,其中副本0是主節點,副本3是失效節點,而C是客戶端。

 
PBFT算法流程

4.3 垃圾回收

爲了節省內存,系統須要一種將日誌中的無異議消息記錄刪除的機制。爲了保證系統的安全性,副本節點在刪除本身的消息日誌前,須要確保至少f+1個正常副本節點執行了消息對應的請求,而且能夠在視圖變動時向其餘副本節點證實。另外,若是一些副本節點錯過部分消息,可是這些消息已經被全部正常副本節點刪除了,這就須要經過傳輸部分或者所有服務狀態實現該副本節點的同步。所以,副本節點一樣須要證實狀態的正確性。

在每個操做執行後都生成這樣的證實是很是消耗資源的。所以,證實過程只有在請求序號能夠被某個常數(好比100)整除的時候纔會週期性地進行。咱們將這些請求執行後獲得的狀態稱做檢查點(checkpoint),而且將具備證實的檢查點稱做穩定檢查點(stable checkpoint)

副本節點保存了服務狀態的多個邏輯拷貝,包括最新的穩定檢查點,零個或者多個非穩定的檢查點,以及一個當前狀態。寫時複製技術能夠被用來減小存儲額外狀態拷貝的空間開銷。

檢查點的正確性證實的生成過程以下:當副本節點i生成一個檢查點後,向其餘副本節點廣播檢查點消息<CHECKPOINT,n,d,i>,這裏n是最近一個影響狀態的請求序號,d是狀態的摘要。每一個副本節點都默默地在各自的日誌中收集並記錄其餘節點發過來的檢查點消息,直到收到來自2f+1個不一樣副本節點的具備相同序號n和摘要d的檢查點消息。這2f+1
個消息就是這個檢查點的正確性證實

具備證實的檢查點成爲穩定檢查點,而後副本節點就能夠將全部序號小於等於n的預準備、準備和確認消息從日誌中刪除。同時也能夠將以前的檢查點和檢查點消息一併刪除。

檢查點協議能夠用來更新水線(watermark)的高低值(h和H),這兩個高低值限定了能夠被接受的消息。水線的低值h與最近穩定檢查點的序列號相同,而水線的高值H=h+k,k須要足夠大才能使副本不至於爲了等待穩定檢查點而停頓。加入檢查點每100個請求產生一次,k的取值能夠是200。

4.4 視圖變動,改朝換代

使用計時器的超時機制觸發視圖變動事件

視圖變動協議在主節點失效的時候仍然保證系統的活性。視圖變動能夠由超時觸發,以防止備份節點無期限地等待請求的執行。備份節點等待一個請求,就是該節點接收到一個有效請求,可是尚未執行它。當備份節點接收到一個請求可是計時器還未運行,那麼它就啓動計時器;當它再也不等待請求的執行就把計時器中止,可是當它等待其餘請求執行的時候再次情動計時器。

相關文章
相關標籤/搜索