# Tendermint中的共识协议-给Paxos打上byzantine补丁

By [CaseyLuo](https://paragraph.com/@luoyx) · 2022-03-29

---

Tendermint是一个通用的byzantine共识协议层，把上层的应用抽象为一个复制状态机，只要共识协议对要执行的事件和事件顺序达成共识，状态机依照次序回放事件，最终各个节点存储的状态就能够达成一致。

和常见的分布式数据库、存储等系统中常用到的共识协议paxos，raft等不同，Tendermint能够允许byzantine节点存在的环境中运行，而paxos，raft等则不行，基于他们实现的系统无法解决byzantine节点带来的问题。

而又与比特币，以太坊中使用的POW共识协议不同的点，则在于POW是一种基于链式的应用模型。基于链式的应用模型状态可能会随时出现分叉，而且参与共识的节点数量不受限制，系统中不存在一个最终的状态确认点，只能是基于“合法最长链原则”。

由于tendermint的应用模型更加接近与paxos，raft，因此为了方便大家理解tendermint共识中的核心知识，我会采用和paxos对比的方式来讲解tendermint的共识。

在paxos的实现中，集群机器数为2f + 1，可接受的故障机器数为f，因此只要故障的节点不过半数，共识协议就能继续推进。paxos协议分为两个阶段：prepare/commit。prepare阶段中proposer会向多数派的accepter协商本轮的<proposal id, value>，如果value为空，则proposer可以自定义要提交的value，否则proposer只能选择acceptor返回的<proposal id, value>中最大proposal id对应的value。接着，在commit阶段中，proposer会将协商好的<proposal id, value>广播给多数派的accepter，acceptor收到消息后校验并提交value。在整个流程中可能会存在并行执行的多个proposer，因此他们发起的<proposal id, value>之间可能会存在冲突，acceptor解决冲突的原则是只提交proposal id更大的value，当多数派的acceptor都提交同一个value后，value最终被chosen。

paxos协议中之所以能够保证正确性，是因为当多数acceptor提交一个value后，proposer因为要通过prepare来选择<proposal id, value>，而prepare要发给多数派的acceptor，必定和某个提交了value的acceptor存在交集，因此proposer无论如何都无法发起一个除value之外的commit请求，从而保证了正确性。

在上述描述paxos节点的行为的时候，都假设了节点会按照协议要求的行为来执行，否则正确性则无法得到保证。例如proposer如果直接无视了prepare阶段中收集到的<proposal id, value>，而直接采用自己选定的value'来发起commit，则会出现已经被提交的value被value'替换的问题。

为了解决上述节点不按协议“办事”的问题，单纯依赖分布式协议是不够的，首先需要引入密码学的工具。为了防止byzantine节点恶意篡改其受到的<proposal id, value>内容，<proposal id, value>中还需要带上节点用私钥对其进行的签名，accetpor在校验受到的消息时，不能只因为proposalid大于本地值，就接受其value，还需要验证其proposalid来源的<proposal id, value>消息的签名是否合法，以及是否存在恶意节点对不同的<proposal id, value>执行多次签名投票的情况。

除了消息带上签名，其次还需要故障和byzantine节点的个数均为f，并且小于集群机器数的1/3。当然故障节点f和byzantine节点的f可能存在交集，但是假设一种极端情况，故障f与byzantine f无交集，为了能够使得还在工作的节点中正常节点仍然能够达成多数派，因此要求集群机器为3f+1。当集群机器为3f+1时，即使f的机器出现故障，剩下的2f+1节点中，byzantine节点依然小于半数，从而无法破坏协议。

最后，需要对paxos协议做一点修改，当acceptor在受到commit消息后，不能直接接受proposalid更大的value，而是检查最新受到的<proposalid, value>是否在prepare阶段被多数派签名过，acceptor只接受被多数派签名过的<proposalid, value>，并在后续锁定该value。后续接受到prepare请求时，也会将该<proposalid, value>签名并返回。因此，当多数派acceptor在commit某个<proposalid, value>后，byzantine节点将无法再有机会伪造出另外一个合法的<proposalid, value'>，因为value'无法得到多数派的签名。这里提一句，在tendermint中prevote/precommit阶段分别对应paxos中的prepare/commit阶段，在tendermint里commit阶段是最终chosen阶段，但paxos的commit阶段并不是最终chosen阶段。

在给上述paxos协议打上3个补丁后，就可以升级为byzantine的tendermint共识协议了～

---

*Originally published on [CaseyLuo](https://paragraph.com/@luoyx/tendermint-paxos-byzantine)*
