共识算法
由于是完全异步的系统,无法保证某一时刻所有节点知道全部事件,那么如何对事件进行全局排序,进而对事件内的交易进行排序。对于没有leader的大部分拜占庭容错协议需要通过交互投票信息来达成共识,甚至要求多轮投票。hashgraph不需要发送投票信息,每个节点根据hashgraph数据可以在本地根据确定性函数计算出事件的全局排序,并可以保证各节点得出的排序是一样的,从而实现不需要发送消息达到共识的目的。
当然,Alice和Bob在任何时刻都可能不会拥有相同的hashgraph数据,因此,通常是对较早的事件的hashgraph数据才能完全匹配,对于最近的事件节点之间可能相互看不见。还可能存在一个新事件传播出去后被记录在hashgraph数据中较早期的位置。对于这些问题,hashgraph通过虚拟投票来解决。
假设Alice拥有hashgraphA,Bob拥有hashgraphB,在某一时刻, 这两个数据存在轻微差别,但最终会达到一致。一致性意味着如果A和B都包含事件x,那么必定包含x的完全相同的祖先,以及祖先之间的关系。如果Alice知道事件x,而Bob不知道,并且他俩都是诚实的,我们就期望Bob尽快通过gossip协议学习到事件x, 并且共识算法假设这最终肯定会发生的,但无法保证何时发生,因为协议是完全异步的,也不存在事件传播的超时机制。
Alice通过对hashgraphA中的事件进行一系列的“选择”,计算一个事件的总排序。在每个“选择”中,A中的一些事件视为形成投票,另一些事件视为接受投票。Alice计算多个“选择”,一个事件可能参与多个“选择”,并在不同“选择”中形成不同的投票。如果事件是Bob创建的,我们就说Bob在某个“选择”中进行了投票。当然该投票是虚拟的, 不是Bob发出来的,但效果跟Alice收到Bob的投票一样。
Bob还有一种欺骗的方式创建事件:基于自我祖先z创建2个事件x、y(在正常情况下,y的自我父事件应该是x),这意味着x,y不在一条链上了,形成了分叉。然后,Bob将x传播给Alice,将y传播给Carol,当然他们不知道存在分叉, 所以,Alice计算Bob对事件x产生的虚拟投票和Carol计算Bob对事件y产生的虚拟投票将是不一致的。在这种情况下,存在一小段时间中Alice的hashgraph中包含x,但不包含y。Bob的hashgraph中包含y,但不包含x。
hashgraph共识算法通过一个“看见”和“强看见”(strongly seeing)的概念来解决上述分叉攻击。首先,区分2个定义:祖先和自我祖先。祖先是指其他节点传播过来的事件,自我祖先是最后一次收到信息而创建的事件。下图中,深蓝色顶点是红色顶点的自我祖先,浅蓝色顶点是祖先:
如果事件w的祖先包括事件x,但没有包含事件y,我们叫做w可以“看见”x,不能”看见”y。
而如果x,y都是w的祖先(x,y处于分叉状态),则w无法“看见”x或y,以及其他任何由Bob创建的事件。换句话说,w可以看见x,表示x的创建者不存在分叉。
如果全网存在n个节点(n>1),那么事件w可以"强看见"事件x,意思是:事件w被≥2n/3的不同节点上的事件看见,而这些事件也都可以看见事件x。举例如下:
上面显示了4种黄色事件可以“强看见”橘色事件的场景。黄色事件可以看见红色事件, 红色事件可以看见橘色事件,并且红色事件分布于不同节点,超过了2n/3,即3个以上。
“强看见”意味着虚拟投票是可行的:分布在不同节点上的红色事件其实就代表了该节点对该事件的签名投票确认。
在虚拟投票中,事件x对一些YES/NO的问题进行投票(例如,某个事件是不是知名的),计算投票的依据就是事件x的所有祖先。如果w可以“强看见”x,x就会发出投票给w。例如,黄色事件发起一个提问,询问可以“强看见”的橘色事件,请求橘色事件进行投票操作,因为黄色事件可以“强看见”橘色事件,橘色事件就将投票发给黄色事件,这个投票信息是拜占庭容错的,保证无法造假。
进一步来说,如果hashgraphA和hashgraphB是一致的,就不可能存在一个事件在A中强看见事件x,而在B中强看见事件y。这也是拜占庭个容错证明的理论基础。这也确保了如果一个攻击者试图通过分叉来欺骗,他们也不能使得不同的节点得出不同的事件顺序。历史上一些拜占庭协议要求对收到的投票进行”转发”,防止Alice向Bob和Carol发送不同的投票,这种防止攻击的机制和“强看见”的机制是类似的。
根据上述的定义, 完整的hashgraph共识算法如下:
主流程很简单:
并行运行2个循环:
loop:
同步所有已知的事件给随机的节点
end loop
loop:
接收一个信息同步
创建一个新的事件
call divideRounds
call decideFame
call findOrder
end loop
简单的gossip协议已经足够了,但还是有可以提高效率的空间。例如Alice和Bob相互告知他们所知道的全部信息,然后Alice发送她最新知道而Bob不知道的信息,协议可能要求Alice按照逻辑顺序发送事件,这样Bob可以在收到每个事件时都知道该事件的父事件,协议也可以要求Bob收到同步事件后立即发送同步事件给Alice,协议也可以要求一次和多个节点进行同步。这些优化可以在具体实现时可以采用也可以不采用。
每次同步后,调用3个处理函数,最终确定事件的顺序。这里没有节点之间的交互,全部靠本地计算完成。在这些处理中, 每个for循环中以逻辑顺序访问事件:先访问父事件,再访问子事件。在第一个循环中(注:函数divideRounds,划分轮次),如果是第一个事件:设置 x.round=1,x.witness=TRUE
。算法定义一个常量n(n>1),代表全部节点;定义常量c,要求≥2,例如10;
divideRounds的过程如下:
procedure divideRounds
for each event x //从最早的时间开始遍历
r ← x的父事件中较大的轮数 (没有父事件则置为1)
if x可以“强看见”>=2n/3的r轮的见证人
x . round ← r + 1
else
x . round ← r
x . witness ← ( x 没有自我父事件 ) //true
or ( x . round > x . selfParent . round ) //true
事件x被创建后,会分配一个round,再计算一个它是否是见证人的bool值。
以上图为例,如果最底层的一行是r轮,那么图中除了黄色事件之外剩下的所有事件都处于r轮,黄色处于r+1轮, 因为他可以“强看见”r轮的事件。历史中的第一个事件的round是1。每个事件最终都会有一个创建时轮次和接收时轮次(round created 和 round received),创建时轮次简称为上面的轮次,即变量x.round。
对于任何给定的节点,每一轮中创建的第一个事件叫做见证人。只有见证人可以发送和接收虚拟投票。见decideFame的过程如下:
procedure decideFame
for each event x in order from 按照轮数从前往后一次排序所有事件
x.famous ← UNDECIDED // 未确定该事件是不是知名见证人
for each event y in order from 按照轮数从前往后一次排序所有事件
if x.witness and y . witness and y . round>x . round
d ← y.round - x . round //x和y的相差轮数
s ← 在 y.round-1 轮中y可以强看见的见证人事件集合
v ← s中的投票结果 (超过2/3投票:TRUE)
t ← s中的投票数量
if d = 1 // 第一次选举
y . vote ← can y see x ? //如果y可以看见x,投票
else
if d mod c > 0 // 正常投票流程:c>=2, 即要求 至少已投过1次
if t > 2* n /3 // 绝大多数见证人投票了,表示是知名见证人,保存投票结果
x . famous ← v
y . vote ← v
break out of the y loop //y投票完成,跳出循环,
else // 直接投票, x是不是知名见证人还需要下一次继续投票
y . vote ← v
else // this is a coin round
if t > 2* n /3 // 绝大多数赞成,进行投票
y . vote ← v
else // else flip a coin 随机选择
y . vote ← middle bit of y . signature y签名的中间位置的值
对于每个见证人,需要决定是否是知名的(x . witness=true),这个决定通过虚拟投票完成。每个节点在本地运行虚拟投票的过程,它将hashgraph中的事件看做是节点间彼此发送的投票,在每一轮中,节点对见证人进行投票,直到超过2/3节点的节点都投票同意。
在见证人所在轮次的下一轮次中大部分见证人可以看见该见证人,那么就叫该见证人为知名见证人,否则只是普通见证人。
拜占庭协议对每个见证人进行一个选举来决定它是否知名:对于在r轮次中的见证人x,每个在r+1轮次的见证人对x进行投票,表示他们能否看见见证人x(注:就是上面d=1时的第一次选举)。超过2/3的见证人都能看见的话,全网就决定它为知名见证人,选举过程结束。
如果投票需要更加平衡,那么它会根据需要继续进行多轮投票,每个见证人可以根据大多数见证人在上一轮中可以”强看见”的情况,在正常轮次投票中进行投票。为了防止攻击者控制网络,还有一个随机投票轮,这意味着即使攻击者控制了所有网络上的消息,使得投票分裂(keep the votes carefully split),还是有机会使得全网随机达到超过2/3的票数。从而协议达到最终一致性。
如果满足d=1,这需要继续下一个计算,在d=2时判断是否是知名见证人。在那个修改过的算法中,每个选举都会开始新的一轮。如果两者在以下混合算法中组合,它甚至会继续工作。
在每一轮中,首先运行所有的选举(d=1)。如果该轮中每个见证人是否是知名见证人都已经确定,并且在那一轮中少于2/3的节点创造了知名见证人,那么选举将全部重新运行,进入d=2。对于这个混合算法,本文中的所有理论都能成立,包括拜占庭容错证明。
对于新的选举,共识时间会变长,大约20%,但实际中很少发生,即使真的发生,也会增加知名见证人的数量确保公平性。
一旦达成共识,或者指定轮次中每个见证者都是知名的,那就很容易确定一个共识时间戳和对时间的全局排序。这个工作在findOrder中完成:
procedure findOrder
for each event x
if r轮或r轮之前中不存在事件y:y. witness = TRUE 和 y. famous = UNDECIDED
and x 是每个r轮唯一知名见证人的祖先
and this is not true of any round earlier than r
then
x. roundReceived ← r
s ← set of each event z such that z is
a self - ancestor of a round r unique famous
witness , and x is an ancestor of z but not
of the self - parent of z
x. consensusTimestamp ← median of the
timestamps of all the events in s
return all events that have roundReceived not UNDECIDED ,
sorted by roundReceived , then ties sorted by
consensusTimestamp , then by whitened signature
首先,计算接收轮次。 事件x有一个接收轮次r,是指所有的独特知名见证人都是事件x的子孙辈的那个轮次,并且该轮次中每个见证人是否是知名的都在轮次≤r中确定了。 (某一轮次中独特知名见证人的定义是:在该轮次的知名见证人集合中,去除该轮次中拥有多个知名见证人的节点)。
然后,计算接收时间。 假设事件x的接收轮次是r,并且Alice在r轮次中创建了一个独特知名见证人y。 该算法就是找到这样的事件z,它是y的自我祖先中最早学习到x的自我祖先。 设t是Alice创建z时放入z的时间戳。 那么t可以被认为是Alice声称第一次学习x的时间。 x的接收时间是r轮中唯一知名见证人的所有创建者的时间戳的中间数。
然后计算共识顺序。 所有的事件都按他们的接收轮进行排序。 如果两个事件有相同的接收轮,那么它们按照他们收到的时间排序。 如果仍然无法排序,那么根据签名数据进行排序:对接收轮中所有唯一知名见证人的全部签名进行异或。
(注: 最后的描述不是特别理解, 等加深理解后再用更通俗的语言描述)