Tendermint共識協議詳解

本文爲萬向區塊鏈技術中心研究組撰寫。

 

1. Tendermint簡介

Tendermint被設計用於構建各種分佈式應用,易於理解和使用,並且高效。


Tendermint基於狀態機副本複製技術,適用於區塊鏈的賬本存儲。它是BFT(拜占庭容錯)的,能夠容許不超過1/3 的拜占庭節點的存在,也就是說,在這個前提下,系統能夠保證所有正常節點擁有相同的交易列表,並按相同的順序執行交易,最終得到相同的狀態。


Tendermint 包含兩個主要的模塊:

(1)共識引擎稱作Tendermint Core, 用於保證各節點按相同的順序記錄相同的交易列表。

(2)應用接口稱作Application BlockChain Interface,簡稱 ABCI 。ABCI允許開發者使用任意的編程語言和開發環境編寫應用邏輯,例如交易的處理邏輯等。

 

通過將共識引擎與應用邏輯解耦,Tendermint 可以用於構建各種分佈式應用,包括各種區塊鏈等。例如
Cosmos、 Ethermint 和 Hyperledger Burrow 等都基於 Tendermint 共識引擎而構建。

 

2. Tendermint共識算法

本部分詳細介紹Tendermint共識算法。 Tendermint共識算法屬於拜占庭容錯類的共識算法,在拜占庭節點數不超過 1/3的情況下,能夠保證共識的安全性(safety)。嚴格來說,由於系統中 validator節點的投票權(voting power)可能不一樣,因此,容錯性更準確地說,是指拜占庭節點的投票權不超過 1/3。


2.1. Tendermint共識算法

2.1.1. 系統模型

系統中,節點分爲兩種類型:validator和非validator節點。

validator節點參與共識,也就是對區塊(包含一批交易)進行共識,包括propose區塊,對提議的區塊進行投票。而非validator節點不參與共識,但會幫助傳播區塊和投票消息,以及相互同步狀態等。

節點之間不一定兩兩相連,和一個節點直接相連的那些節點稱作peers。

無論是validator節點,還是非validator節點,都包含與共識過程相關的一些狀態,如區塊鏈當前高度height,round,以及step等。

節點之間運行gossip協議,相互同步共識狀態和區塊鏈狀態信息。

 

2.1.2. 狀態機概覽

2.1.2.1. 算法主體流程

Tendermint算法主流程如下所示:

 

上圖大致描述了共識的過程:從Propose區塊開始,進行Prevote和Precommit兩個階段的投票。如果投票達成共識,則依次進入Commit 和 NewHeight階段,完成共識,區塊鏈高度增加。整個過程稱爲一個round。不管投票是否達成共識,系統將推進到下一個round,進行新一輪的共識。這其中的區別是:

(1)如果是完成NewHeight 階段,則下一個round 用於共識下一個高度的區塊

(2)而如果當前round投票沒有達成共識,則下一個round仍將共識當前高度的區塊。

 

注意到上圖右下角有一幅跳舞的圖片,這是一種捷克民間舞蹈,叫波爾卡,英文名爲polka。Tendermint算法中,各validator節點的共識過程有點類似於跳波爾卡舞蹈。事實上,在第一階段 Prevote投票中,如果節點收到了+2/3的Prevote投票,這些投票整體上就稱爲一個polka,也叫做PoLC。

 

2.1.2.2. 算法相關概念

從上述過程可以看出,Tendermint共識過程基於round進行。基於區塊鏈的當前高度,每個新區塊的共識需要一個或多個round。

 

關於round

一個round包括3個階段(step):Propose,Prevote和Precommit。整個Tendermint共識過程就是由一個或多個round,再加上兩個特殊的階段:Commit和NewHeight所組成,如下所示:

NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->...

其中,階段序列:
(Propose -> Prevote -> Precommit)

稱爲一個round。

 

一個共識過程可能需要多個round的原因可能有:

(1)指定的proposer節點不在線

(2)proposal block無效

(3)節點沒有及時收到proposal block

(4)雖然proposal block有效,但是沒有足夠多的節點在Precommit 階段及時收到對應的 +2/3 的prevotes

(5)雖然proposal block有效,也有足夠多的節點接收到了+2/3 的prevotes,但是沒有足夠多的節點收到+2/3 的 precommits

 

類似上述情況的解決方法有:

(1)系統變更到下一個round,重新指定proposer節點

(2)round變更時,增加各階段的超時時間

 

2.1.2.3. 算法詳細流程

下面詳細介紹算法的流程。

 

1、Propose 階段(height:H,round:R)

在這一階段,指定的proposer節點組裝並廣播proposal(提議內容)。

 

說明:

(1)proposer節點指定方式爲:依據當前的round和各validator的投票權,採取確定性的、非阻塞的roundrobin選擇算法來選取。

(2)proposal包含一個提議的區塊,以及一個可選的、最新的PoLC-round(小於當前的round值R)。PoLC-round的含義是,針對該round,有一個PoLC,即有+2/3(多於2/3)的節點的prevote投票。

 

僅在當前proposer節點知曉一個最新的PoLC-round時,纔會將其包含在proposal中。 這裏PoLC-round的作用是,必要時讓節點從更舊的round 解除鎖定,保持系統的liveness(活性)。


Propose 階段結束條件爲:

(1)超時時間內收到proposal block及PoLC-Round的所有prevote 投票(如果有的話),則立即轉到下一階段:Prevote(H,R);

(2)否則,等到timeoutProposeR超時, 轉到下一階段: Prevote(H,R)。這裏可以看到,節點在Propose 階段會等待一小段時間:timeoutProposeR,來接收proposal。這是Tendermint所基於的一個弱同步假設。除此之外,算法的其餘步驟爲異步的。

(3)公共退出條件

 

上述公共退出條件具體指的是:

收到針對特定block的 +2/3 的precommit 投票,則轉到Commit(H)

收到(H,R+x)的任何 +2/3 的 prevote 投票,則轉到Prevote(H,R+x)

收到(H,R+x)的任何 +2/3 的 precommit 投票,則轉到Precommit(H,R+x)

 

上述公共退出條件在接下來將介紹到的 Prevote 和Precommit 階段也都會起作用。

 

2、Prevote 階段 (height:H,round:R)

每個validator節點進入到Prevote 階段後,會投prevote 投票並向其他節點廣播其投票。

首先,如果節點自LastLockRound起lock在某一block,此時卻有PoLC-Round的、另一個block或nil的PoLC,則節點unlock。這裏 LastLockRound < PoLC-Round < R;

否則,如果節點還lock在某一block,則針對該block 進行prevote(投票並廣播,之後所有類似表述都可以類比理解);

否則,如果proposed block有效,則對其prevote;

否則,如果proposed block無效,或者 沒有及時收到,則prevote nil。

 

Prevote 階段的結束條件爲:

(1)收到任何+2/3 的prevote 投票後等待一段時間:timeoutPrevote。

如果該時間內收到針對一個特定block或nil的+2/3 的 prevote 投票,則立即轉到下一階段:Precommit(H,R);

如果該時間內沒有收到上述PoLC,則轉到下一階段:Precommit(H,R)。

(2)公共退出條件

 

3、Precommit 階段 (height:H,round:R)

每個validator節點進入到Precommit 階段後,會投 precommit 投票並廣播其投票。

首先,在(H,R),如果節點有針對一個特定block的PoLC,則節點lock在該block,並且置LastLockRound =R,同時precommit 該block;

否則,在(H,R),如果節點有針對 nil 的一個PoLC,則節點unlock,同時precommit nil;

否則,節點將保持其目前的lock狀態不變(即如果已經lock了,則繼續lock,否則,繼續保持unlock的狀
態) ,並且 precommit nil。


Precommit 階段的結束條件爲:

收到任何+2/3 的 precommit 投票後等待一段時間:timeoutPrecommit,如果該時間內收到針對nil的+2/3的precommit 投票,則相當於沒有共識出有效區塊,立即轉到Propose(H,R+1);

否則,如果超時後也沒有收到針對特定 block 的+2/3 的 precommit 投票,則相當於也沒有共識出
有效區塊,轉到Propose(H,R+1)。

公共退出條件

 

4、Commit 階段 (height:H)

這一個階段中,節點嘗試對區塊進行commit:

首先設置CommitTime = now()

然後,節點等待接收完整的、將要 commit 的區塊,然後轉到 NewHeight(H+1) 階段。

 

5、NewHeight 階段 (height:H)

在這一階段,節點最終完成整個共識過程的最後環節,使鏈的高度增加一個區塊,具體步驟爲:

置LastCommit=Precommits(即+2/3的Precommit 投票的集合);

增加區塊高度,將共識出的新區塊添加到鏈上;

置StartTime = CommitTime+timeoutCommit;

等待直到StartTime,以便接收延遲的commits.

最後,轉到 Propose(H,0),開始新的高度上的區塊的共識。注意,此時round的編號又從0開始。

 

以上即爲Tendermint共識算法的主要流程,其狀態機概覽如下:

 

3. Background Gossip

前面已經提到,系統中節點分爲參與共識的validator節點和普通的非validator節點。後者雖然不參與共識,但是通過gossip協議,這些非validator節點會幫助轉發相關的元數據、提議的區塊信息、其他區塊的信息,以及投票信息等。

 

所有的節點都保存相應的共識狀態信息,如當前的 height, round 和 step 等,系統據此進行工作。

 

節點之間經由Connection進行連接,同時,Connection由多個channel構成。節點之間就是基於其中的一些channel來運行gossip協議,使得各節點同步到當前最新的共識狀態。

 

以下爲Tendermint系統中gossip協議的一些特性:

節點相互gossip proposed block的PartSet包含的不同數據包,並且基於LibSwift 優化數據傳輸,提升速
度;

節點相互gossip prevote/precommit 投票;

節點相互gossip其知曉的PoLC的prevote投票;

節點向高度落後的節點gossip落後的區塊的commits;

節點時不時地gossip HasVote消息到其連接的 peer 節點以告知其已收到的投票信息;

節點向鄰近節點廣播其當前的state

 

本文參考資料
[1] https://tendermint.readthedocs.io/en/master/
重點章節:
Tendermint 101/Introduction
Tendermint 201/Specification/Byzantine Consensus Algorithm,Genesis, Validators

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章