以太坊的“类-Kademlia”协议

自从这个系列的最后一次更新到现在已经过去好几个月,英文章节迟迟不出,我也跟着暂停了。这段空当利用闲余时间做了一个跟踪虚拟币的小应用 yield.watch,顺便也研究了一下 LocalEthereum 的安全策略。回过头来再看看这个系列,题材不错,看的人还不少(至少是我自己简书专栏里面的热门文章了),就此烂掉真的是太可惜了。所以,我打算后面的内容就自己生产了。

通过前面四个部分的介绍,我们了解了以太坊节点发现协议的一些基础知识,也实现了PingNodePongFindNeighborsNeighbors等数据结构,接下来我们就要开始具体深入的了解该协议的详细内容,包括节点距离计算,存储和查找等。本篇先介绍一些原理性的东西做个铺垫,对后续代码理解也有很大帮助。

分布式散列表(DHT)

如果让你编写一个 P2P 的聊天程序,你会怎么做?你可能首先想到的是,让每个客户端都到一台中央服务器先注册自己的信息(IP 地址和端口),我们称之为路由信息,这样,其中一个客户端要和目标建立 P2P 通讯的时候只要先连接到这台中央服务器询问一下对方信息就可以了。这种实现是最高效的,但是它的最大缺陷是这个中央服务器存在风险,比如被挟持篡改或断网下线。有什么办法可以绕过中央服务器找到目标信息?

广播,我把它称为找熟人。其中一个客户端先找自己“熟人”(已经连接的客户端)询问目标信息。“熟人”如果不知道目标信息,可以再找他们的“熟人”询问,一级一级找下去,最终一定也能找到目标。这种实现虽然绕过了中央服务器,但是却效率低下,还有可能会引起“广播风暴”或者“泛洪”,对整个系统是一个灾难。

那么有没有一种办法既可以绕过中央服务器又可以避免“广播”的弊端呢?

这个时候就该 DHT 粉墨登场了。它结合了前面的两种思路,把中央服务器的路由信息分成许多份,分布存储在每个客户端上,查找的时候先在本地路由信息里面查找,没有的话再选一个路由询问,直至找到目标。这是我对 DHT 的通俗解释,实际操作上要复杂得多,它的协议实现也是五花八门,我现在要讲的就是其中比较流行的一种——Kademlia

Kademlia协议

Kademlia 是一种基于距离算法的 DHT,这个“距离”并不是地理意义的距离,而是算术意义上的,它以异或运算作为计算距离的依据。异或运算我们见得比较多的是用在对称密码学里面,比如著名的 One Time Pad,因为它有一个特性是:sec ^ key = enc(加密); enc ^ key = sec(解密)。

还有很多独特的性质,使得它在算术和节点树上具有实际意义。一个对象我们用一个唯一的二进制的 ID 来表示,那么 ID 间异或运算反映的就是 ID 中比特的差异情况,也可以说是对象的差异,因为这里的 ID 就是对象的基因,基因差异不就是个体差异嘛,而且越靠前(高位)的比特对最终的差异结果影响的权值越大。如果把这样的 ID 统统竖起来,高位比特朝上,低位比特朝下,同一分支下比特一样的束在一起,我们就可以得到一棵二叉树,这时候,这种差异值就体现了这棵树的叶子(树枝末端)之间的相邻距离。

 

5位比特的数字 ID

Kademlia 中使用的是160 位比特的数字作为节点 ID,这个数字空间可以达到 2¹⁶⁰,整颗树太庞大了,电脑是无法存储的。所以我们只能按一定规则将这棵大树拆分成许多小树,分别存储在各个节点上,用于路由查询。具体拆法就是对这颗树的各比特位 i160>i≥0),每一个节点只存储不超过 k 个与自己相距在 [2^i, 2^(i+1)) 范围内的节点信息(ID,IP 地址和端口),我们把它叫做 k-桶。k-桶实际上就是节点的路由表。

k-桶的生成过程如图所示:

 

5位比特的 ID,k 为2的情况下,节点 C 生成路由表的过程

总的来说,一开始只有一个 k-桶,它包含了整个 ID 数字空间。当我们探测到一个节点的时候,我们计算一下跟自己的距离然后把它加到现有的相应的 k-桶中,如果:

  • k-桶没满,把它加到 k-桶末尾。
  • k-桶满了,如果:
    • k-桶包含节点自己,这个k-桶向下(节点树枝方向)一分为二,直到不可再分为止(即 i=0)。
    • k-桶不包含节点自己,取出最早插入的节点重新 PingNode,没有响应的话就把它删除掉,新的节点加到 k-桶末尾。

Kademlia 尽量多地储存“离自己更近的节点”的路由设计保证查询最终是收敛的。

以太坊的“类-Kademlia”协议

以太坊的节点发现和网络结构并没有严格按照 Kademlia 协议实现,它是一种 类-Kademlia 的协议,顾名思义就是类似但不尽相同。它们之间主要区别在于:

  • Kademlia 定义了四种操作:

    • PING:探测一个节点是否在线
    • STORE:令对方储存一份数据
    • FIND NODE:根据节点 ID 查找一个节点
    • FIND VALUE:根据键查找一个值(数据)

    但 类-Kademlia 里面并不需要FIND VALUESTORE数据包结构。

  • 类-Kademlia 里面数据包是经过签名的。

  • 类-Kademlia 节点直接拿它的公钥(256位比特)作为其 ID。

  • 参数的不同:

    • 行(跟 k-桶类似)大小是16(即 Kademlia 的k参数)
    • FindNeighbors并发数是3(即 Kademlia 的alpha参数)
    • 路由每跳8位比特(即 Kademlia 的b参数)
    • PingNodeFindNeighbors请求超时时间是300ms(这里指的是一个来回的时间,从发出 PingNode 到返回对应的 Pong,从发出 FindNeighbors 到返回Neighbors的时间)
    • k-桶空闲刷新间隔是3600s(所谓空闲就是一个k-桶已经有相当一段时间没有发生任何节点增加或驱逐的变化了)

怎么理解这些参数呢,在 Kademlia Peer Selection 有说到,类-Kademlia 中的路由表由行组成,一开始是1行,最多可以到255行。每一行包含 k 个节点信息,k 设定大小决定了网络的冗余程度。如果某一行包含的节点 ID 具有相同的比特前缀位数是 i 的话,我们就把这一行标号为 i。这里的 i 和 Kademlia 论文提到的 i 是不一样的,Kademlia 论文中的 i 值越高,表示异或距离越大,而这里的 i 值越高,表示具有相同比特前缀的位数越多。当行满的时候,如果这行包含自身节点或它的行号不是 b 的整数倍,它就分裂成两行,否则它就只做一些节点更新替换。我们把这个 b 形象地称为“路由每跳比特位数”。

类-Kademlia 当 b=2 时路由表生成过程



 

相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页