摘要
我在上一篇文章《无链之链:Corda带来的新视角》当中,留下了一个最主要的问题(也许是两个):Corda需要共识机制吗?如果需要,这个共识机制应该是什么样子的?这个问题我相信很多人都会关心:一方面notary这种近乎信任中介的模式,为什么还需要共识机制呢?另一方面R3又一直宣称Corda也是有共识机制的,这不能不让人产生困惑。前一段听到R3的企业产品部门负责人Mike Ward简介Corda的时候,受到相关概念的启发,又重新开始考虑这个问题,并且有了一些新的想法。
本文首先详细介绍一下基于notary的交易验证机制,而后解释一下STXO的概念以及其对系统实现的影响,最后再针对Corda到底需不需要共识机制进行分析,并给出笔者建议的一种共识机制实现方式的建议。
1. Notary模式回顾与分析
本节实际上是针对Corda的notary机制进行详细讲解,毕竟“无链之链”这篇综述性的文章,并不能全面描述notary运行机制。考虑到notary是Corda至关重要的特点之一,同时也为了后面进一步讨论共识机制,对notary模式的关键要点和流程进行详细介绍还是有必要的。
Notary的基本运行方式
Corda基于notary进行交易确认的模式,可以简单地这样理解:一个notary记录了通过他确认的所有交易的输入、输出状态,新来一个交易的时候,只需要对所有(成功的)历史交易的输入项进行一个直接的查询,就能知道新交易的所有输入项中任何一个是否以前被使用过。只要所有输入项都没有被使用过,也就是不会发生双花,整个交易(在输入项这个维度上)就是合法的。
另一方面,Notary机制的要点之一,就是允许系统中存在多个这样的角色,从而能够让相互之间不做交易的人使用不同的notary作为公证人。例如:Alice和Bob做交易,使用notary N1,Carol和Dave做交易,使用notary N2。这样的设计才使得notary这个概念有意义,如果整个Corda网络都必须采用同一个notary的话,几乎就退化成为了一个星形结构(哪怕notary本身是一个大型的分布式网络,逻辑上仍然是一个整体),中心化网络的一切弊病都会回来了,也就没必要发明notary这个“新词“了。
Corda的发明者们提出:通过上面这两个机制,Corda实现了隐私局部化、高性能、高可靠性等关键特性。因此可以说这两点是Corda的notary机制的核心特性,缺一不可。
多个Notary的问题
首先设想一个场景:Alice从Bob那里获得了10元钱,要通过一个交易把这十元钱的资产转移给Dave的时候,这个交易应该通过哪个notary来确认呢?如果Corda对于notary确认交易的机制没有其他规定的话,N1、N2都能对这个交易做确认:根据我们前面对notary机制的简单解释,如果这笔资产没有在之前转移给别人,那么N1、N2当然都不可能在自身的数据库中查到使用过的记录,因此都会认为这个10元钱的资产状态是有效的。真的这么简单吗,怎么隐约感觉会有点什么问题呢?
我们来考虑第二个场景:Alice想要实现“双花”攻击,也就是把这笔资产同时转给Carol、Dave,他会怎么做?此时如果他选择N1来做这两笔交易确认的话,N1作为验证者,无论是采用任何同步、串行等机制,一定会实现根据历史状态查出这个资产已经转移过一次的信息,从而拒绝这两笔交易中还未成功的那个,这是notary模式的应有之义。因此,对Alice而言,很显然应该选择将这两笔交易分别发送给N1、N2进行确认,期望有可能进行攻击。可以想见,如果系统没有特殊机制应对这种情况,而是任由N1、N2通过简单的历史状态查询方式来对这笔资产的有效性进行验证,由于查不到被使用过的记录,一定都认为它是有效的,结果就发生了“双花”。因此,系统必须设计一套机制来保证这两个交易至少有一个不能成功。什么样的机制呢?我们先来看看Corda关于notary进行状态确认的原则:
- 状态必须是基于notary的,也就是每个状态会有一个notary“字段”,表明这个状态当前“基于”哪个notary
- 一个合法交易的所有输入必须基于同一个notary
先来看第一条:由于每个状态都有一个notary属性,因此一个notary是能够判断这个状态是不是“属于”自己的。有了这个信息,上面问题中所说的交易对于N1、N2来说就不是完全等同的,如果我们能够根据这个信息设定一个机制来防止双花,就能解决这个问题了。很显然,我们只能要求那个发现待验证交易的一个输入项对应的notary不是自己的时候做出反应(在这个例子中是N2),他可以有两个选择:一是验证失败,本质上就是拒绝验证那些包含notary属性不等于自己的状态的交易,因为这笔资产的状态对应的notary一定是N1。另一选择是他可以在验证这个交易的时候根据该状态的notary属性向“原”notary(也就是N1)来发出请求,针对这个状态进行验证。这两个方案看起来应该都能满足防双花的需求,但稍微想一下就知道,方案二需要引入很复杂的机制才能实现,这个留给读者自行验证。毫无疑问,Corda选择了前一个方案,就是直接拒绝。
Notary变更
上面的方案解决了一个问题,就是跨notary的“双花”,但是故事并没有结束。我们考虑第三个场景:Alice从Bob和Carol那里分别收到10块钱,其中与Bob的交易是N1确认的,与Carol的交易是N2确认的。他要把这20块钱给Dave,需要怎么做呢?我们先说合理的做法:他应该发起一笔与Dave的交易,以两个10块钱的资产作为输入,形成一个20块钱的输出,这也是UTXO模型的标准做法。但是根据上面的讨论,我们会发现一个问题:这笔交易无论是发给N1确认还是N2确认,都应该会遭到拒绝——这也就是上述关于状态验证的两个原则中的第二条所规定的含义——如果不拒绝,就无法杜绝Alice把其中一个十块钱通过另一个notary进行双花的风险。
当然,我们可以有另一种做法,就是要求Alice创建两个交易,每个交易只有一个输入项,然后分别交给当初生成他们的交易所对应的notary来确认。这样做是没问题的,但是如果系统只允许这一种方案,就意味着Corda系统中的UTXO永远不能“合并”,本质上Corda系统会被notary划分成一个个永远无法互相通讯的子网,每个子网中都有所有那些需要互相交易的参与方。大家可以脑补一下这种景象,notary+UTXO模型的“弊端”被“平方级”放大,应该属于系统设计不良的范畴。
因此,只剩下一种可能性了:Corda必须提供一个可以把一笔资产/一个状态/一个UTXO在不同notary之间转移的手段。读过“无链之链”这篇综述文章的读者应该还记得,Corda专门设计了一种交易类型——notary变更交易,用于将一个状态在两个notary之间转移。同时,Corda也内置了一个“Notary变更流程”的实现——NotaryChangeFlow,这个flow接收两个参数:originalState, newNotary,主要完成以下工作:
- 创建一个交易(类型显然是notary变更交易),以变更前的状态作为输入,产生一个变更后的新状态作为输出
- 获得这个状态的所有可能消费者的签名(其实就是这个状态的拥有者,目前我还没看到Corda当中支持一个状态有多个拥有者的设计,严格说这也不符合UTXO模型。不过不排除考虑可撤销的状态算是有两个可能消费者的情况?总之这是笔者的理解)
- 从状态的原notary那里获取签名
- 完成该交易并将结果发送给参与方(也就是上面所说的所有可能消费者/拥有者),这样原旧状态的拥有者就拥有新状态了
简单地说,一个状态进行notary变更,就是状态的拥有者通过该状态的当前notary跟自己做了一个交易,这个交易的输出状态与输入状态之间只有一个变化,就是notary从旧的变成了新的。这样做显然有一个好处,就是旧的状态也符合系统的规则:在交易完成之后就无效了,从而至少在旧notary的覆盖范围内防止通过notary变更实现双花。
有了这套机制,我们先回过头来看第二个场景——因为我们要保证这个新机制不能产生新的双花攻击的机会:Alice想要把一笔(已经转移给Dave的)资产转移给Carol,如果他使用N1来验证:此时N1会因为该资产已经转移而拒绝;如果他使用N2来验证,要么因为notary不同而被拒绝,要么必须首先进行notary变更。而根据notary变更流程,他同样要跟N1做一个交易把资产的notary变成N2,N1仍然能知道这个状态已经在此之前交易过了,所以会拒绝这个变更交易,从而仍然能够防止双花。
现在来看第三个场景:Alice只要执行若一次notary变更交易,无论是把从Bob那里收到的10块钱变更到N2上,还是把从Carol那里收到的10块钱变更到N1上,就可以在对应的notary上验证这个新构造的、有两个输入和一个输出的交易,从而实现转账。同时,后续无论通过什么方式进行双花攻击,也都能被拒绝,读者可以自行验证。
小结
简单地说,Corda基于notary进行交易确认方式,在系统只有一个notary的情况下,通过对历史交易记录进行查询的方式就可以实现。一旦系统中有多个notary,并且出现“跨”notary的交易活动,防止双花的的职责当然就涉及多个notary之间的“协同”。Corda结合交易场景,采用了一个比较标准的方式,就是不在交易过程中让多个notary互动,而是在交易之前必须把交易所需要的所有输入状态指定到验证该交易的notary上,避免一个状态可以在两个notary上进行验证的情况,从而实现了防止双花。为此,Corda不仅设置了一种notary变更的交易类型,还内置了变更流程,使得这些功能成为系统框架的一部分。