本文關(guān)于tendermint的共識部分赡鲜,主要參考如下:
1. Ethan Buchman:Tendermint: Byzantine Fault Tolerance in the Age of
Blockchains
2. tendermint中文文檔
3.tendermint的一個論文
4. 萬向區(qū)塊鏈對參考0中論文的翻譯
5. tendermint官方doc
本文著重介紹tendermint的共識方面的細(xì)節(jié)睬塌。
tendermint共識狀態(tài)機(jī)解析
上圖中每個step,其實(shí)都是共識狀態(tài)機(jī)中的一個狀態(tài)昆庇,狀態(tài)轉(zhuǎn)換周期如下:
NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->...
gossip 都廣播哪些內(nèi)容末贾?
我們知道在驅(qū)動狀態(tài)機(jī)最重要兩個因素,一個是通信(tendermint中的P2P是以gossip協(xié)議和reactor模式為基礎(chǔ))整吆;另一個就是event(設(shè)計上任何異步通訊的過程都離不開event)拱撵,這兩部分的內(nèi)容后面有機(jī)會的話會單獨(dú)寫文章解析,現(xiàn)在我們先來看看gossip都廣播哪些內(nèi)容表蝙,以使節(jié)點(diǎn)都能跟進(jìn)當(dāng)前的共識狀態(tài)拴测。
以下引用自官方文檔,逐條說明本人結(jié)合源碼對其的解析:
- Nodes gossip
PartSet
parts of the current round's proposer's proposed block. A LibSwift inspired algorithm is used to quickly broadcast blocks across the gossip network.
說明:partset代表block的一些簡要信息府蛇,其結(jié)構(gòu)如下:
type PartSet struct {
total int
hash []byte
mtx sync.Mutex
parts []*Part
partsBitArray *bits.BitArray
count int
}
- Nodes gossip prevote/precommit votes. A node
NODE_A
that is ahead ofNODE_B
can sendNODE_B
prevotes or precommits forNODE_B
's current (or future) round to enable it to progress forward.
說明:這條要結(jié)合下面狀態(tài)機(jī)的prevote集索、precommit狀態(tài)解析中的step ends一起理解。因?yàn)閜revote汇跨,precommit都需要2/3+的票數(shù)通過抄谐,才能進(jìn)行狀態(tài)轉(zhuǎn)換。
- Nodes gossip prevotes for the proposed PoLC (proof-of-lock-change) round if one is proposed.
說明:gossip prevotes的另一個用處是polka unlock.
- Nodes gossip to nodes lagging in blockchain height with block commits for older blocks.
說明:gossip block扰法,落后的節(jié)點(diǎn)同步新block。
- Nodes opportunistically gossip
HasVote
messages to hint peers what votes it already has.
說明:節(jié)點(diǎn)偶爾gossip HasVote消息
- Nodes broadcast their current state to all neighboring peers. (but is not gossiped further)
說明:節(jié)點(diǎn)gossip 當(dāng)前所在狀態(tài)(未來版本不再gossip此部分)
共識狀態(tài)解析
接下來讓我們逐條分析各個共識狀態(tài)毅厚。
1. propose step(height:H,round:R)
客戶端發(fā)起一筆交易gentx塞颁,通過rpc發(fā)到tendermint節(jié)點(diǎn)后進(jìn)入mempool cache,tm調(diào)用checktx的abci接口驗(yàn)證交易吸耿,如果驗(yàn)證成功就進(jìn)入mempool祠锣,proposer節(jié)點(diǎn)選擇打包交易到區(qū)塊中,并將區(qū)塊附到proposalMsg中咽安,對此msg進(jìn)行簽名伴网,然后通過gossip把此msg廣播到區(qū)塊鏈網(wǎng)絡(luò)中。
一個proposal包含一個(H,R)的block妆棒,和一個可選的最近的PoLC-Round < R澡腾,當(dāng)然前提是proposer知道此可選參數(shù)(PoLC:proof of lock change,指的2/3+ prevote某個block)糕珊,這樣適當(dāng)保住了共識網(wǎng)絡(luò)的liveness动分。
Upon entering Propose:
指定的proposer 提議了一個block(H,R).
The Propose step ends:
- timeoutProposeR 超時,則進(jìn)入-->Prevote(H,R)
- 當(dāng)收到了proposal block红选,并且改validator的所有prevote step 都在PoLC-Round澜公,則進(jìn)入-->Prevote(H,R)。(要理解這點(diǎn)喇肋,重點(diǎn)要理解某個validator有可能involve在若干個round坟乾,相當(dāng)于說你其他輪中都過了prevote的step迹辐,才能進(jìn)入新一輪的prevote step?但如果有鎖的情況下即使進(jìn)入了prevote輪甚侣,你也不能prevote了)
- After common exit conditions
Note:
注意每輪共識開始時明吩,validator節(jié)點(diǎn)在本地會有一個本地同步時鐘,用來記時ProPose timeout, 如果在timeout時間周期內(nèi)沒有收到proposer節(jié)點(diǎn)的提議渺绒,對該提議的預(yù)投票為空(nil)贺喝,validator節(jié)點(diǎn)會投票決定是否進(jìn)入下一輪共識,validator也可以通過治理模塊投票移出或者替換拜占庭驗(yàn)證者宗兼。而propose timeout也會隨著每輪共識而增加躏鱼。所以可以理解為此過程是同步過程。
但是在其后的prevote和precommit的兩階段共識是完全異步的殷绍,從而減少對時鐘同步的依賴染苛,或者網(wǎng)絡(luò)延遲。但這也同樣存在一個問題主到,如果這兩階段共識中就是得不到1/3以上validator的投票茶行,整個區(qū)塊鏈網(wǎng)絡(luò)將癱瘓(此處其實(shí)有待看源碼仔細(xì)研究,因?yàn)檫@兩個階段也是有timeout定時器的登钥,重點(diǎn)關(guān)注step end的條件畔师,以此來分析可能引起網(wǎng)絡(luò)hang住的場景)。這個問題的本質(zhì)其實(shí)就是FLP不可能定理牧牢。FLP 原理實(shí)際上說明對于允許節(jié)點(diǎn)失效情況下看锉,純粹異步系統(tǒng)無法確保一致性在有限時間內(nèi)完成。
所以tendermint其實(shí)在一定程度是假定節(jié)點(diǎn)中大部分都是正常節(jié)點(diǎn)塔鳍。
2. prevote step (height:H,round:R)
每個節(jié)點(diǎn)收到propose消息后伯铣,會先對區(qū)塊進(jìn)行驗(yàn)證,如果驗(yàn)證通過才開始prevote簽名轮纫,然后廣播到網(wǎng)絡(luò)中腔寡。
Upon entering Prevote, each validator broadcasts its prevote vote.
- First, if the validator is locked on a block since LastLockRound but now has a PoLC for something else at round PoLC-Round where LastLockRound < PoLC-Round < R, then it unlocks.(這條好理解,就是解鎖條件掌唾,重點(diǎn)還是要理解異步)放前。
- If the validator is still locked on a block, it prevotes that. (這條是因?yàn)殒i的規(guī)則,他只能prevote自己lock住的block郑兴,但是會引起重復(fù)prevote犀斋?這個還得結(jié)合源碼研究)。
- Else, if the proposed block from Propose(H,R) is good, it prevotes that.(沒啥好解釋的情连,就是驗(yàn)證了proposal后正常prevote)
- Else, if the proposal is invalid or wasn't received on time, it prevotes <nil>.(proposal無效叽粹,或者定時器超時,就prevote<nil>)
The Prevote
step ends:
- After +2/3 prevotes for a particular block or
<nil>
. -->; gotoPrecommit(H,R)
- After
timeoutPrevote
after receiving any +2/3 prevotes. --> gotoPrecommit(H,R)
- After common exit conditions
3. Precommit Step (height:H,round:R)
Upon entering Precommit, each validator broadcasts its precommit vote.
- If the validator has a PoLC at (H,R) for a particular block B, it (re)locks (or changes lock to) and precommits B and sets LastLockRound = R.(說明了什么時候上鎖)
- Else, if the validator has a PoLC at (H,R) for <nil>, it unlocks and precommits <nil>.(說明了什么時候precommit nil,簡言之虫几,就是獲得了POLC的<nil>)
3.Else, it keeps the lock unchanged and precommits <nil>.(最后剩下的情況也就是超時了吧锤灿?)
A precommit for <nil> means "I didn’t see a PoLC for this round, but I did get +2/3 prevotes and waited a bit".(這句可以理解為沒有獲得非nil的POLC,而且等待了timeout時間段)
The Precommit step ends:
- After +2/3 precommits for
<nil>
. --> gotoPropose(H,R+1)
(注意辆脸,并沒有commit nil但校。prevote nil、precommit nil進(jìn)入下一輪啡氢,因此tendermint中沒有類似PBFT中的viewchange ) - After
timeoutPrecommit
after receiving any +2/3 precommits. --> gotoPropose(H,R+1)
(我理解此處+2/3 precommits是筆誤状囱?應(yīng)該是+2/3 prevotes?) - After common exit conditions
Common exit conditions
- After +2/3 precommits for a particular block. --> goto Commit(H)
- After any +2/3 prevotes received at (H,R+x). --> goto Prevote(H,R+x)
- After any +2/3 precommits received at (H,R+x). --> goto Precommit(H,R+x)
第一點(diǎn)倘是,可理解為在R內(nèi)要追趕最新狀態(tài)搀崭,盡快commit瘤睹。
后兩點(diǎn)可理解為要追趕更新的R轰传。
4. Commit Step (height:H)
- Set CommitTime = now()
- Wait until block is received. --> goto NewHeight(H+1) //注意此處要等到本輪的塊到了才會進(jìn)入NewHeight step.
5. NewHeight Step (height:H)
Move Precommits to LastCommit and increment height.
Set StartTime = CommitTime+timeoutCommit
Wait until StartTime to receive straggler commits. --> goto Propose(H,0)
小結(jié)
鎖
在異步網(wǎng)絡(luò)中获茬,一個最大的問題就是同高度上锦茁,在不同的round提交了不同的block码俩。tendermint引入了鎖的機(jī)制稿存。
什么時候鎖瞳秽?
每個validator鎖定在他獲得PoLC的block中练俐,然后只能對該block precommit,在后面的R中燕锥,如果沒有unlock也只能prevote此block。
什么時候解鎖托慨?
提交了一個當(dāng)前鎖定的塊后
在下一輪(準(zhǔn)確說應(yīng)該是PoLC round)看到PoLC(proof of lock change暇榴,在新的一輪收到大于2/3 prevote的block)時獲得解鎖, 解鎖后不會在PoLC round 投票了蔼紧,因?yàn)樵谠揜中已經(jīng)獲得了polka歉井,也就是說已經(jīng)結(jié)束了prevote state哩至。
鎖定規(guī)則
驗(yàn)證者只能預(yù)投票(pre-vote)他們被鎖定的區(qū)塊(因?yàn)樗谀砇ound中prevote的block未必和precommit的block是相同的)菩貌。這樣就阻止驗(yàn)證者在同一個高度箭阶,上一輪中預(yù)提交(pre-commit)一個區(qū)塊嘹叫,之后又預(yù)投票了下一輪的另一個區(qū)塊诈乒。
Proof of Safety
假設(shè)有-1/3的Byzantine voting power(-1/3指不到1/3), 在R輪時,validator commit一個block B怕磨。這就意味著有1/3+的誠實(shí)節(jié)點(diǎn)lock在了R' > R, 他們直到獲得在R`>R 的POLC才能unlock喂饥。但是這種unlock的情況不會發(fā)生肠鲫,因?yàn)槭S嗟?2/3個節(jié)點(diǎn)能投票給B以外的block,無法形成POLC导饲。(這不是會導(dǎo)致誠實(shí)節(jié)點(diǎn)一直鎖嗎氯材?從而網(wǎng)絡(luò)hang住棠枉?)