什么是Tendermint共识算法
Tendermint 是一种基于拜占庭容错 (Byzantine Fault Tolerant, BFT) 的区块链共识算法,旨在实现高性能、高安全性的分布式状态机复制。它由 Tendermint 团队设计,因 Cosmos 模块化区块链项目的使用而被人熟知。
Tendermint可以理解为PBFT算法的一个变种与优化,优化的主要目的是降低PBFT算法的复杂度,包括逻辑实现的复杂度以及算法复杂度。
Tendermint共识算法流程
下图是来自tendermint官方的一张状态流转图,很清晰的展示了整个共识的流程。
Height:理解为出块的高度,出一个块,Height+1。
Round:在每一个Height中,也就是共识出块当中,可能会出现共识失败的情况,一个round就代表该高度下的一轮共识,可能会出现在一个Height下多轮才成功出块的情况。
常规共识流程
如上图所示,Tendermint共识算法主要流程如下:
- Propose:区块提议者(proposer)也就是其中一个validator提议一个区块,并发送给其他验证者。
- Prevote:其他validator接收到后校验区块的合法性,如果合法就广播prevote消息。
- Precommit:validator接收到超过2/3节点的prevote消息,如果合法就广播Precommit消息。
- Commit:validator接收到超过2/3节点的Precommit消息,就本地执行区块,并写入存储。
视图切换(view-change)
Tendermint共识算法的一大创新就在将view-change与普通共识过程统一了起来,如果常规共识流程的某一个阶段未达成共识,按照图中所示,验证者会广播 Nil ,该 Nil 消息会流转下去,进入下一个 round。这里有一个关键点是每一个round,tendermint都会更换proposer。那么其实本次round结束,不管本次round共识是否达成都会view-change。
超时的处理
在proposal阶段,非区块提议者节点会启动一个定时器T,如果在一段时间内(超时时间是动态调整的),没有收到本次round proposer发送的消息,那么就投票给一个特殊的空块,共识的流程会在空块的继续上继续进行prevote、precommit、commit,只不过到了commit阶段不会将空块写入区块链,直到下一轮选出一个新的区块提议者,相当于本轮空转了。
锁的概念
这里view-change与普通共识流程统一后就会有一个问题?如果当前round共识失败了,那下一个新的proposer如何处理上一个round提出的遗留下的block呢?
例如在precommit阶段,有一部分验证者收到了超过2/3的投票,有一部分没收到。那么收到的那部分验证者就会认为在该Height上达成了一致。没收到的验证者则等待下一个round。那么如果新的区块提议者(proposer)作恶,在该Height上面,提交一个新的区块,那么就有一部分节点会在该Height上产生“二义性”不知道是用上一个round提供的区块,还是新的区块了。
所以Tendermint共识算法规定,如果一个验证者接收到了超过2/3的Prevote消息(也就是该验证者需要发出precommit该区块的消息),该验证者就被锁定在该区块。
那么接下来的共识流程就如下图所示:
如果有一部分节点被锁定,那么新的Proposer如果是被锁定的节点,就广播上一个Proposer提出的区块+锁定证明(自己已经接收到超过2/3节点关于该区块的Prevote证明),那么不管该节点是否锁定,都会投票给上一个Proposer提出的区块。
如果新的Proposer不是被锁定的节点,它在同一个Height提议了新的区块,就有两种情况,一种是被锁定的节点少于1/3,那么当被锁定的节点收到超过2/3节点的对新节点的投票时,它解锁投票给新区块。另一种情况就是这些节点数量可能大于1/3,无法收到超过2/3节点的对新节点投票,那么本次round也将无法达成共识,只有通过不断轮换 Proposer 使其最后落在一个锁定在迁移区块的验证者上面,从而达成一致。
Tendermint共识算法的关键点
- 每一轮都会更换Proposer,将view-change和普通共识流程统一起来,使整个共识流程变的简单。
- 通过锁定机制,保证系统安全性。
与PBFT的主要区别
- PBFT需要显式的视图变更消息,节点间交换大量状态和日志,保证状态一致性。而Tendermint在设计上实现了出块流程与视图切换的无缝结合(统一),避免了复杂状态同步。
- PBFT在进行view-change的时候需要传递大量的信息(需要传递历史执行信息),但是Tendermint不用,所以相对于PBFT O(n^3) 的复杂度,Tendermint 是 O(n^2) 的,复杂度的不同就差在传递消息量上面。
简单代码实现
来自AI,供学习使用
import random
class Node:
def __init__(self, node_id, total_nodes):
self.id = node_id
self.total_nodes = total_nodes
self.locked_block = None # 已锁定区块(高度的当前锁)
self.locked_round = -1 # 锁定的轮次
def propose_block(self, round_number):
# 提议区块由 proposer 产生,简单用字符串表示
block = f"Block_H{height}_R{round_number}_Proposer{self.id}"
print(f"Node {self.id} proposes block: {block}")
return block
def prevote(self, block, round_number):
# 判断是否投票给该区块,模拟简单规则:
# 如果有锁定区块且锁定轮次 >= 当前轮次,则投票锁定区块
# 否则投票给提议区块(假设有效)
if self.locked_block and self.locked_round >= round_number:
print(f"Node {self.id} prevotes for locked block: {self.locked_block}")
return self.locked_block
else:
print(f"Node {self.id} prevotes for proposed block: {block}")
return block
def precommit(self, prevotes, round_number):
# 统计 prevotes 投票结果
vote_counts = {}
for vote in prevotes:
vote_counts[vote] = vote_counts.get(vote, 0) + 1
# 查找得票超过 2/3 的区块
threshold = (2 * self.total_nodes) // 3 + 1
for block, count in vote_counts.items():
if count >= threshold and block != "nil":
self.locked_block = block
self.locked_round = round_number
print(f"Node {self.id} precommits block: {block}")
return block
print(f"Node {self.id} precommits nil")
return "nil"
def run_tendermint_consensus(nodes, height):
round_number = 0
committed_block = None
while True:
print(f"\n=== Height {height} Round {round_number} ===")
proposer_id = round_number % len(nodes)
proposer = nodes[proposer_id]
# Propose 阶段
proposed_block = proposer.propose_block(round_number)
# Prevote 阶段
prevotes = []
for node in nodes:
vote = node.prevote(proposed_block, round_number)
prevotes.append(vote)
# 统计 prevote 投票是否达到 2/3
vote_counts = {}
for vote in prevotes:
vote_counts[vote] = vote_counts.get(vote, 0) + 1
threshold = (2 * len(nodes)) // 3 + 1
# Precommit 阶段
precommits = []
if any(count >= threshold and block != "nil" for block, count in vote_counts.items()):
# 找出获得超过 2/3 的区块
for block, count in vote_counts.items():
if count >= threshold and block != "nil":
block_to_commit = block
break
for node in nodes:
precommit_vote = node.precommit(prevotes, round_number)
precommits.append(precommit_vote)
# 统计 precommit 投票
precommit_counts = {}
for vote in precommits:
precommit_counts[vote] = precommit_counts.get(vote, 0) + 1
if precommit_counts.get(block_to_commit, 0) >= threshold:
committed_block = block_to_commit
print(f"\nBlock committed at height {height}: {committed_block}")
break
else:
print("No block got enough prevotes, moving to next round")
round_number += 1
if __name__ == "__main__":
total_validators = 4 # 4 个节点
height = 1
# 初始化节点
nodes = [Node(i, total_validators) for i in range(total_validators)]
run_tendermint_consensus(nodes, height)




文章评论