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

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共识协议了~