1. 程式人生 > >拜占庭共識演算法PBFT:Practical Byzantine Fault Tolerance

拜占庭共識演算法PBFT:Practical Byzantine Fault Tolerance

論文地址:http://pmg.csail.mit.edu/papers/osdi99.pdf
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)只在發生失效的情況下使用(viewchange
and new-view messages)。作者不僅提出演算法,而且使用這個演算法實現了一個拜占庭容錯的NFS服務。
作者列舉一下這邊論文的貢獻:

1)首次提出在非同步網路環境下使用狀態機副本複製協議
2)使用多種優化使效能顯著提升
3)實現了一種拜占庭容錯的分散式檔案系統
4)為副本複製的效能損耗提供試驗資料支援

2.系統模型

系統假設為非同步分散式的,通過網路傳輸的訊息可能丟失、延遲、重複或者亂序。作者假設節點的失效必須是獨立發生的,也就是說程式碼、作業系統和管理員密碼這些東西在各個節點上是不一樣的。

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

系統允許攻擊者可以操縱多個失效節點、延遲通訊、甚至延遲正確節點。但是不能無限期地延遲正確的節點,並且算力有限不能破解加密演算法。例如,不能偽造正確節點的有效簽名,不能從摘要資料反向計算出訊息內容,或者找到兩個有同樣摘要的訊息。
message digests:https://www.techopedia.com/definition/4024/message-digest

可以理解為checksum, 對檔案內容進行hash, 如果檔案內容被修改則hash改變, 用以驗證檔案是否損壞/被修改. MD5, SHA等演算法都可以用作生成message digest的演算法)

Cryptographic hash function

密碼學中將輸入資料作為message, 輸出資料, 即hash value, 記做message digest = 訊息摘要.
僅對message digest進行簽名, 而不是對整個message進行簽名.
什麼是簽名
非對稱加密中使用私鑰對訊息+自己的身份進行加密, 外部節點使用公鑰進行解密, 可以看到加密方的身份, 即為簽名

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)來實現私密性,因為引數和部分狀態對服務操作來說是不可見的。
推理
由於有f個壞節點, 它們可能完全不回覆訊息, 所以我們要確保客戶端只要接收到n-f個訊息就足以做出判斷. 不能依賴於更多的資訊, 否則等待永不回覆壞節點就意味著liveness被破壞.
考慮一個最差的情況: 那f個還未回覆的節點是好節點, 收到的n-f個訊息中有f個壞節點訊息. 那麼接到的好節點的訊息實際上是 n-2f個, 壞節點訊息是f個. 根據少數服從多數, 需要n-2f>f, 即n>3f.
綜上, 全網總節點數n至少是3f+1才能確保安全性和Liveness.

4.演算法

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

所有的副本在一個被稱為檢視(View)的輪換過程(succession of configuration)中運作。在某個檢視中,一個副本作為主節點(primary),其他的副本作為備份(backups)。檢視是連續編號的整數。主節點由公式p = v mod |R|計算得到,這裡v是檢視編號,p是副本編號,|R|是副本集合的個數。當主節點失效的時候就需要啟動檢視更換(view change)過程。Viewstamped Replication演算法和Paxos演算法就是使用類似方法解決良性容錯的。
重要概念: View.
節點執行過程中, 全網路的配置(configuration)是在不斷變化的. 比方說現在的配置是A節點是主節點, 其餘節點都是從節點, 那麼一段時間後, 這個配置可能改變為B節點為主節點, 其餘從節點.
我們將每一次的配置狀態稱為一個View. 整個網路就是不斷在不同的View之間切換的.
記當前View的編號為v, 編號為p = v % n的節點就作為主節點, 其餘作為從節點. 當主節點失效時, 進行View的切換.

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)的訊息
  • 當前檢視編號v。
請求開始

當主節點p收到客戶端的請求m,主節點將該請求向所有副本節點進行廣播,然後展開一個三階段協議(three-phase protocol)。在這裡,如果請求太多, 主節點會將請求buffer起來稍後再處理。

三階段協議

我們重點討論預準備(pre-prepare)、準備(prepare)和確認(commit)這三個階段。pre-prepare和prepare兩個階段用對同一個檢視中的requests進行排序(即使對請求進行排序的主節點失效了),prepare和commit兩個階段用來確保在不同的view之間的requests是嚴格排序的。

預準備階段

在預準備階段,主節點(primary)分配一個序列號n給request,然後向所有backup node傳送PRE-PREPARE訊息,PRE-PREPARE訊息的格式為<<PRE-PREPARE,v,n,d>,m>,這裡v是檢視編號,m是客戶端傳送的請求訊息,d是請求訊息m的摘要。

請求m本身是不包含在預準備的訊息裡面的,這樣就能使預準備訊息足夠小,因為預準備訊息的目的是作為一種證明,確定該請求是在檢視v中被賦予了序號n,從而在檢視變更的過程中可以追索。另外一個層面,將“將排序的協議”和“廣播請求的協議”進行解耦,有利於對訊息傳輸的效率進行深度優化。

只有滿足以下條件,各個backup node才會接受一個PRE-PREPARE 訊息:

  1. 請求和pre-prepare訊息的簽名都正確, d是m的digest
  2. 當前檢視編號是v。
  3. 該備份節點從未在檢視v中接受過序號為n但是摘要d不同的訊息m。
  4. 編號n在下限h和上限H之間. (這是為了避免壞的主節點通過設定一個超大的n來窮盡編號)
進入準備階段

如果備份節點i接受了預準備訊息<<PRE-PREPARE,v,n,d>,m>,則進入準備階段。在準備階段的同時,該節點向所有backup node傳送準備訊息<PREPARE,v,n,d,i>,並且將PRE-PREPARE訊息和PREPARE訊息寫入自己的訊息日誌。backup node若不接受pre-prepare訊息就什麼都不做。

接受準備訊息需要滿足的條件

包括主節點在內的所有副本節點在收到準備訊息之後,1、對訊息的簽名是否正確,2、檢視編號v是否一致,3訊息序號n是否滿限制這三個條件進行驗證,如果驗證通過則把這個準備訊息寫入訊息日誌中。

準備階段完成的標誌

當且僅當節點i將如下訊息插入到message log後, 我們才認為節點進入準備完畢狀態, 記做prepared(m, v, n, i).

  • 請求m
  • 針對m, v, n的pre-prepare訊息
  • 2f個來自不同從節點(只有backup)的對應於pre-prepare的prepare訊息(算上自己的那個prepare訊息). 如何驗證對應關係? 通過檢視編號v、訊息序號n和摘要d。

為啥要2f個: 因為prepare訊息主節點不參與傳送, 這樣全網3f+1個節點刨去主節點和惡意節點(f+1)個, 剩下的是2f個好節點.

預準備階段和準備階段確保所有正常節點對同一個檢視中的請求排序達成一致。接下去是對這個結論的形式化證明:如果prepared(m,v,n,i)為真,則prepared(m’,v,n,j)必不成立(i和j表示副本編號,i可以等於j),這就意味著至少f+1個正常節點在檢視v的預準備或者準備階段傳送了序號為n的訊息m。
證明:
反證法:假如有prepared(m,v,n,i)為真且prepared(m’,v,n,j)也為真,那麼可以又接受prepared的條件可以看出,有f+1個好節點(包括他本身)發出了prepare訊息接受m,另有f+1個好節點發出了prepare接受m’,這樣好節點為2f+2 但是N=3f+1,好節點只有N-f=2f+1個,有矛盾。

進入確認階段

當prepared(m,v,n,i)條件為真的時候,副本i將<COMMIT,v,n,D(m),i>向其他副本節點廣播,於是就進入了確認階段。每個副本接受確認訊息的條件是:1)簽名正確;2)訊息的檢視編號與節點的當前檢視編號一致;3)訊息的序號n滿足水線條件,在h和H之間。一旦確認訊息的接受條件滿足了,則該副本節點將確認訊息寫入訊息日誌中。(補充:需要將針對某個請求的所有接受的訊息寫入日誌,這個日誌可以是在記憶體中的)。

committed && committed-local

我們定義committed(m,v,n)為真得條件為:任意f+1個好節點進入prepared(m,v,n,i)為真的時候;(不帶節點編號i表示是一個整體狀態)
committed-local(m,v,n,i)為真的條件為:prepared(m,v,n,i)為真,並且i已經接受了2f+1個commit訊息(包括自身在內)和與之對應一致的pre-prepare訊息。commit與pre-prepare訊息一致的條件是具有相同的檢視編號v、訊息序號n和訊息摘要d。

確認被接受的形式化描述

commit階段保證了以下這個不變式(invariant):對某個好節點i來說,如果committed-local(m,v,n,i)為真則committed(m,v,n)也為真。(也就是說只要有一個好節點到了committed-local狀態則整體就達到了committed狀態)
證明:
某個好節點i達到committed-local說明其至少接收了2f+1個節點的commit訊息,所以至少有f+1個好節點的commit訊息。好節點發送commit需要保證是已經進入prepared狀態才行。這樣就至少有f+1個好節點進入了prepared狀態,也意味著整體進入了committed狀態。

這個不變式和檢視變更協議保證了所有正常節點對本地確認的請求的序號達成一致,即使這些請求在每個節點的確認處於不同的檢視。更進一步地講,只要有一個號節點到達committed狀態,那麼至少有f+1個好節點也會到達committed狀態。

故事的終結

每個副本節點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 view-change檢視變更

view-change存在的意義: 主節點壞掉了, 更換view(即更換主節點primary), 以此來保證全網的liveness.使用計時器的超時機制觸發view-change。

從節點觸發View Change

檢視變更可以由從節點timeout觸發,以防止從節點無期限地等待請求的執行。從節點接收到一個請求但是計時器還未執行時,那麼它就啟動計時器並等待其執行,如果它不再等待任何請求的執行時就把計時器停止,如果沒有新的請求過來切執行不結束時,timeout會觸發。

從節點廣播View Change

當計時器超時的時候,會觸發view-change,使v變為v+1,並停止服務(除了接收checkpoint, view-change, and new-view messages)並廣播< VIEW-CHANGE,v+1,n,C,P,i >訊息到所有節點(n為最近的一個stable checkpoint對應的請求號,C表示2f+1個有效的checkpoint的證明,P是一個包含若干個Pm的集合. 其中Pm的定義: 對於編號大於n且已經在節點i prepared的訊息m, Pm包含一個合法的pre-prepare訊息(不包括對應的client message)和2f個對應的, 由不同節點簽名的prepare訊息(v, n, D(m)要一樣))。

新的主節點上位

View v+1的主節點p接收到2f個來自不同其他節點的view-change訊息後, 就廣播<NEW-VIEW, v+1, V, O>_sigma_p到所有節點.
V :集合{p接收到的view-change訊息, p傳送(或者應該傳送)的對於v+1的view-change訊息}
O: 一個包含若干pre-prepare訊息的集合(不順帶請求), 包含如下內容:

  • 主節點從V中找到
  • min-s, 即最近一次stable checkpoint的編號.
  • V中所有prepare訊息的最大的編號max-s.
  • 對min-s和max-s之間的每個編號n, 主節點建立一個新的pre-prepare訊息, 用於View v+1. 兩種情況:
  • V中的某個編號為n的view-change訊息, 其P集合不為空. 這種情況下, 主節點建立一個新的pre-prepare訊息<PRE-PREPARE, v+1, n, d>_sigma_p
  • 其P集合為空. 主節點建立一個新的pre-prepare訊息<PRE-PREPARE, v+1, n, d^null>_sigma_p, 其中d^null是一個特殊的null請求的digest. 該操作什麼都不做.

然後, 主節點將O中的資訊插入到log中. 如果min-s大於它最近一個checkpoint的編號, 主節點要計算編號為min-s的checkpoint的proof, 並將其插入到log中, 然後按照4.3中所講進行垃圾回收.
至此, 主節點正式進入View v+1, 可以接受關於v+1的訊息.

從節點接受新的主節點

如果從節點接受的new-view訊息滿足如下條件

  • 簽名合法
  • V中的view-change訊息合法.
  • O合法 (演算法類似於主節點建立O的演算法)

從節點會將這些訊息加入到log中(跟上一段的主節點操作一樣), 然後對於O中的每一個pre-prepare訊息, 廣播對應的prepare訊息到所有節點並插入log, 正是進入View v+1.