本文爲萬向區塊鏈技術中心研究組撰寫。
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