三個階段:預準備(pre-prepare)策泣、準備(prepare)彰阴、和確認(commit)
步驟:
- 從全網(wǎng)節(jié)點選舉出一個主節(jié)點(Leader)莽囤,新區(qū)塊由主節(jié)點負責生成
- Pre-Prepare:每個節(jié)點把客戶端發(fā)來的交易向全網(wǎng)廣播鸠天,主節(jié)點0將從網(wǎng)絡收集到需放在新區(qū)塊內的多個交易排序后存入列表烁涌,并將該列表向全網(wǎng)廣播镀脂,擴散至123
- Prepare:每個節(jié)點接收到交易列表后牺蹄,根據(jù)排序模擬執(zhí)行這些交易。所有交易執(zhí)行完后薄翅,基于交易結果計算新區(qū)塊的哈希摘要沙兰,并向全網(wǎng)廣播,1->023翘魄,2->013鼎天,3因為宕機無法廣播
- Commit:如果一個節(jié)點收到的2f(f為可容忍的拜占庭節(jié)點數(shù))個其它節(jié)點發(fā)來的摘要都和自己相等,就向全網(wǎng)廣播一條commit消息
- Reply:如果一個節(jié)點收到2f+1條commit消息熟丸,即可提交新區(qū)塊及其交易到本地的區(qū)塊鏈和狀態(tài)數(shù)據(jù)庫训措。
拜占庭容錯能夠容納將近1/3的錯誤節(jié)點誤差,IBM創(chuàng)建的Hyperledger 0.6版本就是使用了該算法作為共識算法(1.0版本已棄用,使用kafka)绩鸣。
這個機制下有一個叫視圖view的概念怀大,在一個視圖里,一個是主節(jié)點呀闻,其余的都叫備份節(jié)點化借。主節(jié)點負責將來自客戶端的請求給排好序,然后按序發(fā)送給備份節(jié)點們捡多。但是主節(jié)點可能會是拜占庭的:它可能會給不同的請求編上相同的序號蓖康,或者不去分配序號,或者讓相鄰的序號不連續(xù)垒手。備份節(jié)點應當有職責來主動檢查這些序號的合法性蒜焊,并能通過timeout機制檢測到主節(jié)點是否已經(jīng)宕掉。當出現(xiàn)這些異常情況時科贬,這些備份節(jié)點就會觸發(fā)視圖更換view change協(xié)議來選舉出新的主節(jié)點泳梆。
視圖是連續(xù)編號的整數(shù)。主節(jié)點由公式p = v mod |R|計算得到榜掌,這里v是視圖編號优妙,p是副本編號,|R|是副本集合的個數(shù)憎账。當主節(jié)點失效的時候就需要啟動視圖更換(view change)過程套硼。
預準備階段
在預準備階段,主節(jié)點分配一個序列號n給收到的請求胞皱,然后向所有備份節(jié)點群發(fā)預準備消息邪意,預準備消息的格式為<<PRE-PREPARE,v,n,d>,m>
,這里v是視圖編號朴恳,m是客戶端發(fā)送的請求消息抄罕,d是請求消息m的摘要。
請求本身是不包含在預準備的消息里面的于颖,這樣就能使預準備消息足夠小,因為預準備消息的目的是作為一種證明嚷兔,確定該請求是在視圖v中被賦予了序號n森渐,從而在視圖變更的過程中可以追索。另外一個層面冒晰,將“請求排序協(xié)議”和“請求傳輸協(xié)議”進行解耦同衣,有利于對消息傳輸?shù)男蔬M行深度優(yōu)化。
備份節(jié)點對預準備消息的態(tài)度:
只有滿足以下條件壶运,各個備份節(jié)點才會接受一個預準備消息:
- 請求和預準備消息的簽名正確耐齐,并且d與m的摘要一致。
- 當前視圖編號是v。
- 該備份節(jié)點從未在視圖v中接受過序號為n但是摘要d不同的消息m埠况。
- 預準備消息的序號n必須在水線上下限h和H之間耸携。
水線存在的意義在于防止一個失效節(jié)點使用一個很大的序號消耗序號空間。
進入準備階段:
如果備份節(jié)點i接受了預準備消息<<PRE-PREPARE,v,n,d>,m>
辕翰,則進入準備階段夺衍。在準備階段的同時,該節(jié)點向所有副本節(jié)點發(fā)送準備消息<PREPARE,v,n,d,i>
喜命,并且將預準備消息和準備消息寫入自己的消息日志沟沙。如果看預準備消息不順眼,就什么都不做壁榕。
接受準備消息需要滿足的條件:
包括主節(jié)點在內的所有副本節(jié)點在收到準備消息之后矛紫,對消息的簽名是否正確,視圖編號是否一致牌里,以及消息序號是否滿足水線限制這三個條件進行驗證含衔,如果驗證通過則把這個準備消息寫入消息日志中。
準備階段完成的標志:
我們定義準備階段完成的標志為副本節(jié)點i將(m,v,n,i)記入其消息日志二庵,其中m是請求內容贪染,預準備消息m在視圖v中的編號n,以及2f個從不同副本節(jié)點收到的與預準備消息一致的準備消息催享。每個副本節(jié)點驗證預準備和準備消息的一致性主要檢查:視圖編號v杭隙、消息序號n和摘要d。
預準備階段和準備階段確保所有正常節(jié)點對同一個視圖中的請求序號達成一致因妙。痰憎。
進入確認階段
當(m,v,n,i)
條件為真的時候,副本i將<COMMIT,v,n,D(m),i>
向其他副本節(jié)點廣播攀涵,于是就進入了確認階段铣耘。每個副本接受確認消息的條件是:
- 簽名正確;
- 消息的視圖編號與節(jié)點的當前視圖編號一致以故;
- 消息的序號n滿足水線條件蜗细,在h和H之間。
一旦確認消息的接受條件滿足了怒详,則該副本節(jié)點將確認消息寫入消息日志中炉媒。(補充:需要將針對某個請求的所有接受的消息寫入日志,這個日志可以是在內存中的)昆烁。
接受確認消息需要滿足的條件
我們定義確認完成committed(m,v,n)
為真得條件為:任意f+1個正常副本節(jié)點集合中的所有副本i其prepared(m,v,n,i)
為真吊骤;本地確認完成committed-local(m,v,n,i)
為真的條件為:prepared(m,v,n,i)
為真,并且i已經(jīng)接受了2f+1個確認(包括自身在內)與預準備消息一致静尼。確認與預準備消息一致的條件是具有相同的視圖編號白粉、消息序號和消息摘要传泊。
確認被接受的形式化描述
確認階段保證了以下這個不變式:對某個正常節(jié)點i來說,如果committed-local(m,v,n,i)
為真則committed(m,v,n)
也為真鸭巴。這個不變式和視圖變更協(xié)議保證了所有正常節(jié)點對本地確認的請求的序號達成一致眷细,即使這些請求在每個節(jié)點的確認處于不同的視圖。更進一步地講奕扣,這個不變式保證了任何正常節(jié)點的本地確認最終會確認f+1個更多的正常副本薪鹦。
故事的終結
每個副本節(jié)點i在committed-local(m,v,n,i)
為真之后執(zhí)行m的請求,并且i的狀態(tài)反映了所有編號小于n的請求依次順序執(zhí)行惯豆。這就確保了所有正常節(jié)點以同樣的順序執(zhí)行所有請求池磁,這樣就保證了算法的正確性。在完成請求的操作之后楷兽,每個副本節(jié)點都向客戶端發(fā)送回復地熄。副本節(jié)點會把時間戳比已回復時間戳更小的請求丟棄,以保證請求只會被執(zhí)行一次芯杀。
使用計時器的超時機制觸發(fā)視圖變更事件
視圖變更將在主節(jié)點失效的時候仍然保證系統(tǒng)的活性端考。視圖變更可以由超時觸發(fā),以防止備份節(jié)點無期限地等待請求的執(zhí)行揭厚。備份節(jié)點在接收到一個有效請求却特,但是還沒有執(zhí)行它時,會查看計時器是否在運行筛圆,如果沒有裂明,那么它將啟動計時器;當請求被執(zhí)行時就把計時器停止太援。如果計時器超時闽晦,將會把視圖變更的消息向全網(wǎng)廣播。
各個節(jié)點會收集視圖變更信息提岔,并發(fā)送確認給 view v+1 中的主節(jié)點仙蛉。新的主節(jié)點收集了視圖變更和視圖變更確認消息(包含自己的信息),然后選出一個checkpoint作為新view處理請求的起始狀態(tài)碱蒙。它會從checkpoint的集合中選出編號最大(假設編號為h)的checkpoint荠瘪。接下來,主節(jié)點會從h開始依次選取h到h+L(L是高低水位之差)之間的編號n對應的請求在新的view中進行pre-prepare振亮,如果一條請求在上一個view中到達了committed狀態(tài)巧还,主節(jié)點就選取這個請求開始在新的view中進行第三階段。但是如果選取的請求在上一view中并沒有被prepare坊秸,那它的編號n有可能是不被同意的,我們選擇在新的view中作廢這樣的請求澎怒。