本文是Corda共识机制系列文章的第三篇,前两篇已经论述了Corda是否需要共识机制,以及需要什么样的共识机制,这一篇将给出Corda共识机制的具体方案。
3. Corda共识机制设计方案
Corda系统应该针对发生notary变更的对象(STXO)达成共识,这是本文第二部分通过理论推导得出的结论,但并没有给出具体的共识方案。在这里,我们首先从实践的角度把这个共识机制的合理性再论述一次,也就自然而然地推导出共识机制的具体实现模式了。
为什么无法针对UTXO达成共识
我们首先来看基于UTXO的共识模式(其实这个名字不精确,严格意义上说应该叫交易阶段共识,后面我们会详细讨论到),也就是典型的区块链系统的共识模式:当你要使用或花掉一个UTXO的时候,各个节点要确认这个UTXO没有被花过,因此要查询UTXO数据库,找到这个对象,才能确认交易。验证节点都同意交易成立,交易(或者交易所属的区块)就被记录在链上,成为了“事实”。
很显然,UTXO共识模式对基于notary的、非广播式的系统是不适用的:以Corda为例,一个状态如果没有在若干notary之间发生转移,那么其当前notary就可以确认他是否以前被使用过,不需要其他notary的参与。另一方面,由于系统的交易是不广播的,系统中的其他notary对这个对象的存在一无所知,也就是文章第二部分提到过的的:对系统中其他notary而言,这个对象既不是UTXO,也不是STXO,所以其他notary也无法参与这个交易过程。
反过来,对于发生过notary变更的对象,基于UTXO的共识模式也有问题:要实现基于UTXO的交易合法性检查,意味着要让所有notary知道变更之后的对象在交易发生时仍然是一个UTXO,也就是说让每个notary都把变更后的UTXO记录在案,用于交易发生时的判断依据。简单来说,这样做确实是可以的,对于防止这个新的UTXO被双花,是有效的,读者可以自行验证。然而,由于广播的是notary变更产生的新UTXO,而那个经过notary变更已经失效的STXO的信息却不被其他notary所知,这样就无法避免文章第二部分所说的,由于各种技术的或者蓄意欺诈的原因,而使得这个对象再次通过变更notary的方式,在其他notary那里生成新的UTXO,从而实现双花——关键点在于,刚刚广播的那个UTXO是上一次notary变更生成的对象,而双花攻击者通过二次notary变更生成的UTXO和上次变更生成的UTXO确实不是同一个对象,所以任何节点都无法通过UTXO的记录判断这个双花。
简单说,无论从有效性还是必要性的角度,针对UTXO达成共识在Corda模式的系统中都是没有意义的。
基于STXO的共识
针对STXO达成共识,也就是说系统中所有的notary都知道某个对象是STXO,后续交易确认时可以作为依据。很显然,对于没有发生过notary变更的对象,他的所有历史信息都在某一个notary那里记录,让系统中的其他notary知道他什么时候变成STXO仍然没意义,因此我们可以只针对发生notary变更的情况来讨论共识的流程:
广播,当一个对象发生notary变更的时候,该对象的当前notary应该把这件事广播通知系统中的所有其他notary,告诉他们“某个对象发生了notary变更,因此他已经变成了STXO,另外会新生成一个UTXO”。其他notary收到这个广播,应该做一系列的处理:
查询,按照交易规则,查询自己的STXO库,看看这个对象是不是在自己这里以前发生过交易,已经是STXO了。注意,我们前面提到过的,在Corda这样的系统中,一个notary对自身STXO数据库的查询结果,只能反映一个对象在当前notary见证过的交易范围内是不是STXO,而在整个系统中是不是STXO,就要靠下面一个动作——“共识”——来完成了。
共识,每个notary能够返回的结果无非三种可能性:查到/没查到/没反馈,看到这里,大家应该会心一笑了,这就是共识机制派上用场的地方:不是所有节点都一定会反馈同一个结果(包括有些节点因为不在线而没有反馈),因此必须要有合适的机制来解决整个系统对“这个对象是不是STXO”最终达成一致的问题,这就是典型的分布式共识场景。实现共识的技术手段,Corda并没有具体规定,根据官方的说法,Corda并不拒绝采用任何有效的共识机制,听起来有点类似Fabric支持可插拔的共识模型,总之我们应该认为共识可以达成。具体的实现技术,我们将在下一节进行讨论。
确认,如果共识的结论是这个对象目前不是STXO,因此可以交易,则系统中的各个notary就可以接受这个交易,并且将该对象记录到自己的STXO数据库中,使其成为已经交易的状态,从而杜绝这个对象未来可能的双花。而后,发布这个变更消息的notary,也应当根据这个共识的结果,最终确认这个变更交易,产生新的UTXO。这样一来,notary变更交易就正式完成了。反过来,如果共识的结论是“这个对象以前就是STXO”,那么各个节点就通过这个共识拒绝了这个交易,他们自身不需要做额外的动作,而发布变更消息的节点,也不应该为这个交易提供确认。
上述共识机制可以用下图表示:
整个图中涉及了两个交易,红色线条标识的第一个交易是notary变更交易,如上所述,这个交易需要“全网”所有notary的共识才能完成,其中共识流程由黑色线条表示。蓝色线条标识的第二个交易是普通交易,其输入是第一个交易的输出,notary已经变成了B,所以可以由notaryB来确认,能够看出这个普通交易是不需要共识的。此外,还有一个具体的执行变更过程,涉及notaryA和B的交互,但是与共识无关,读者可自行验证或阅读Corda相关代码。
若干要点
至此,我们就讲完了针对notary变更的共识机制的基本流程,下面通过几个问题来讨论/总结一下这个共识机制有什么特点:
- 总体效率
很明显,本文介绍的共识机制是“notary变更共识”,也就是说凡是不涉及notary变更的交易,仍然是由交易双方通过notary直接确认的,不需要在多个notary之间达成共识。只有这样,考虑到notary变更的概率,Corda系统的性能才能大幅度超越每笔交易都需要共识的区块链模式,这也是“多中心化”存在的意义。
- 共识保障
既然是共识机制,必然要满足达成共识所需要的各种条件,这一点与其他系统的共识机制是一样的,例如:如果采用PBFT方式,则必须有2f+1个以上的节点是可信的并且在线;如果是采用类似比特币的机制,则要考虑软分叉之后的恢复等。这里笔者只想强调一点,就是在类似Corda这种系统中,必须使用类似PBFT的“强”共识机制,不能允许“分叉”情况的出现。这是因为系统中没有“区块链”这样的全局数据结构,一旦允许分叉,很难设计有效的机制进行恢复,或者说恢复机制一定会导致系统引入非常复杂的处理流程。当然,这个保障在联盟链的系统中应该是比较容易实现的,现有联盟链也基本上都是采用强共识机制。
- 恶意notary
抛开细节不说,Corda引入notary这种新角色,在共识机制中还有没有其他影响呢?我想最基本的就是要看看恶意notary与区块链系统中一般的恶意节点之间有什么差异,例如:notary变更交易根本不发出广播要求达成共识,或者共识过程拒绝了此次变更而notary仍然去完成变更交易。这种情况是普通区块链系统不会出现的,也就是恶意notary的特殊之处。解决的办法也很简单,只需要在规定notary变更交易,必须有“目标”notary的参与即可。具体参与方式可以有多种,最简单的就是这种交易应该让目标notary也参与验证,这样他就能获得这个交易的全部信息,从而知道输入(STXO)的信息和输出(UTXO)的全部信息。未来,当新的UTXO到自己这里来交易时,可以查询STXO在全网的状态,看看是否达成了共识,从而实现对恶意notary的防范,并且仍然保持了只有notary变更交易才需要共识的模式。
除了上述要点之外,笔者提个“小问题”:共识过程中,如果有一个notary发现需要共识的对象在他这里被使用过,从而拒绝这个共识,而其他notary由于认定该对象未被使用过而同意,则最后这个对象会被认定为未使用过,根据共识的定义,这个对象“未被使用过”的共识仍然能够达成。可是如果这个对象“真的”在这个反对的notary那里使用过怎么办呢?系统不是出现错误了吗?在notary这种模式下,是不是该有个什么一票否决机制?这些问题,就留给读者去思考吧。
小结
本节给出了基于notary模式的系统的一种特殊的共识机制——notary变更交易的共识,建立在这样一个前提下:notary模式的系统中普通类型的交易既不需要,也不可能通过共识来进行确认。
这种共识机制本身是标准的,也就是说原则可以采用任何一种一致的共识算法,但是从实践的角度看,由于没有区块链模型的数据结构,必须采用类似PBFT的强共识机制才有意义,否则必然要为解决分叉问题涉及大量额外的处理流程。
此外,为了进一步防止恶意notary的攻击,需要在notary变更流程中设计更多的环节以确保防止双花攻击,但是这与共识机制的处理流程是无关的。
最后,笔者要进行一个“剧透”——本系列文章还有第四部分,在第四部分我们要对共识机制的具体技术实现给出描述,并且还要给出一个令人“惊讶”的事实和相应的解释,敬请期待。