tendermint 拜占庭共识算法

优质
小牛编辑
119浏览
2023-12-01

状态机概述

在每一个区块高度上, 基于多轮协议来决定下一个区块。每一轮有三个步骤(提案(Propose), 预投票(Provote), 预提交(Precommit)), 以及两个特殊的步骤Commit和NewHeight 所以在tendermint的区块打包始终是按着下面的顺序进行的:

NewHeight->(Propose -> Prevote -> Precommit)[一次区块确认可能需要多轮] -> Commit ->NewHeight

(Propose -> Prevote -> Precommit)被称为一轮。 所以说在tendermint中如果想要确认一个新的区块, 必须经历至少一轮这样的顺序。 一些情况下可能需要多轮才能决定出一个区块:

  • 指定的提议者(指轮到其进行提案的验证者)不在线
  • 被提议者提议的区块时无效的
  • 被提议者提议的区块没有按时广播
  • 提议的区块时有效的, 但是没有在规定时间内收到大于2/3个节点进行的预投票。
  • 提议的区块时有效的, 但是没有在规定时间内收到大于2/3个节点进行的预提交。

PoLC

超过2/3的预投票为一个提议的区块或者nill, 将会进行锁定, proof-of-lock-change 。 也就是说当一个验证者在正常提议正常投票之后, 当它收到了超过2/3的投票他就会进入PoLC的锁定。

框图

                        (Wait til `CommmitTime+timeoutCommit`)
                            +-------------------------------------+
                            v                                     |
                      +-----------+                         +-----+-----+
         +----------> |  Propose  +--------------+          | NewHeight |
         |            +-----------+              |          +-----------+
         |                                       |                ^
         |(Else, after timeoutPrecommit)         v                |
   +-----+-----+                           +-----------+          |
   | Precommit |  <------------------------+  Prevote  |          |
   +-----+-----+                           +-----------+          |
         |(When +2/3 Precommits for block found)                  |
         v                                                        |
   +--------------------------------------------------------------------+
   |  Commit                                                            |
   |                                                                    |
   |  * Set CommitTime = now;                                           |
   |  * Wait for block, then stage/save/commit block;                   |
   +--------------------------------------------------------------------+

背景知识

验证节点

tendermint中并不是所有节点都是验证节点(如果均是验证节点也不可能达到那么高的TPS), 只有那些参与上述区块打包过程的节点才会成为验证节点。 但是这并不表示其他节点没有作用。 其他非验证节点可以对相关的区块数据, 提议, 投票进行广播。 保证了网络的健壮性。

共识广播的策略

节点会广播一个提议者在当前轮提交的区块。 但是它只会广播区块数据的Parset部分(tendermint的区块数据结构在之前文档有提到过) 节点会广播预投票和预提交阶段的投票。 如果一个节点处于PoLC(proof-of-lock-change: 指当前验证者已经锁定了自己的改变, 表示在下一轮投票时, 它依然会坚持目前的投票)阶段。 他会在预投票阶段继续投票之前锁定的提议的区块。 节点会向那些之后的节点广播已经提交的区块 节点会找机会广播HasVote指令用于获取对方已经拥有的投票 节点会广播当前它的状态。

何为tendermint提案

提案是有特定的提议者在每一轮进行的将要打包的提议, 对其进行签名和广播。 提案者是确定性的。 一个在(H,R)(H高度R轮)的提案是由一个区块和可选的PoLC组成。 如果有PoLC说明之前此提案已经有超过2/3的节点进行了投票通过。

状态机步骤说明

通用的退出情况

  1. 超过2/3节点预提交投票在(H, R) 跳到Commit(H, R) --- 表示在本轮已经收到了2/3的节点预投票 那么直接可以进行Commit了
  2. 超过2/3节点进行了预投票在(H, R+X) 跳到Prevote(H, R+x) --- 表示有更新的轮进入到了预投票阶段了
  3. 超过2/3的预提交投票在(H, R+X) 跳到PreCommit(H, R+x) -- 表示更新的一轮已经进入到预提交阶段

Propose Step(H, R)

提议者提出一个区块。 根据下面几种情况进入下个步骤:

  1. 提案超时 直接跳到PreVote(H, R)
  2. 已经接收到提议的区块, 并且此提案已经锁定(PoLC) 跳到PreVote(H, R)
  3. 通用的退出情况

Prevote Step (H, R)

每一个验证者进入PreVote阶段时, 会广播自己的预投票。 如果验证者目前处于锁定阶段, 而且现在有一个新的锁定出现, 并且新的锁定比之前的锁定轮数大并且小于当前轮, 那么此验证者解锁 如果验证者处于锁定, 但是不满足上面的情况, 则会直接投票当前锁定的区块 如果验证者不处于锁定阶段,并且当前提案的区块是有效的 则投票给此处收到的提议区块。 如果验证者不处于锁定阶段,并且当前提案的区块是有效的 则投票给nill。

预投票阶段根据下面几种情况进入下一个步骤:

  1. 超过2/3的预投票为此提案区块或者nill 则进入PreCommit(H,R)阶段
  2. 预投票超时 进入PreCommit(H,R)阶段 ---也就是说在规定时间没有收到超过2/3的预投票
  3. 通用退出情况

Precommit Step (height:H,round:R)

在PreCommit阶段, 验证者广播预提交的选票: if 验证者在此轮锁定了区块, 那么它就会进行预提交投票此区块。并且设置LastLockRound=R else if 验证者在此轮锁定了nill, 那么它会解锁并且预提交投票给nill else 保持之前锁定不变, 投票nill

根据情况进入下一个步骤:

  1. 超过2/3的节点预提交为nill 进入ProPose(H, R+1)
  2. 规定时间没有收到足够多的预提交 进入ProPose(H, R+1)
  3. 通用退出情况

Commit Step (height:H)

设置CommitTime = now() 等待这个区块内容接收到, 进入NewHeight(H+1)