以太坊源码分析(14)P2P分析

#概述

Kademlia(简称Kad)是一种分布式哈希表技术,用于建立p2p网络拓扑结构。

基本原理就是以两个节点ID的异或值作为两节点间的距离d,每个节点都将其他节点的信息存储到称之为K桶的表结构中,该表结构按照d的为1的最高bit位分层(可理解为桶索引),每层中存储最多K个节点信息。如下:

| I | 距离范围 | 邻居 |

|:---:|:---------:|:-----------:|

| 0 |[2^0, 2^1 ) | (IP,UDP,NodeID) <br>...|

| i |[2^i, 2^i+1 ) | (IP,UDP,NodeID) <br>...|

节点查找时,通过询问距离自己最近的a个节点,让对方返回距离目标最近的a个节点,重复这个过程直到找到目标节点或者能问的都问了一遍。


 

参考资料:

[references/Kademlia协议原理简介.pdf](references/Kademlia协议原理简介.pdf)

[https://www.jianshu.com/p/f2c31e632f1d](https://www.jianshu.com/p/f2c31e632f1d)

#以太坊中的实现概述

##几个概念

1. 计算距离的ID值,位数代表了有多少个K桶,经典算法中是160位的。在以太坊中,NodeID为节点的PublicKey,__参与距离计算的是NodeID的sha3哈希值,长度256位__

2. K桶的项数不超过K,K值是为平衡系统性能和网络负载而设置的一个常数,但必须是偶数。eth中K=16。

3. 查找邻居节点时,返回节点数最多是a个,a也是为了系统优化而定的参数,eth中 a=3。

##数据结构及存储

* p2p模块使用独立的leveldb持久化存储所有的邻居节点信息,从而节点重新启动时能直接利用历史上已找到的节点。

* 存储的key为NodeID,value为Node结构体,包含IP、UDP、TCP、ID(即NodeID)等信息。

##p2p网络维护的实现

table.go主要实现了p2p的Kademlia协议。其中定义了K桶的结构并实现节点的维护策略。

###启动过程

节点启动时,初始化配置信息后,会启动p2p server,在server启动过程中,会执行udp对象创建、table对象创建、监听udp端口等处理。table对象创建中就包含了启动goroutine执行节点发现及维护的服务。

1. 从leveldb中随机选取若干种子节点(新节点第一次启动时,使用启动参数或源码中提供的启动节点作为种子节点),出入桶结构中(内存);

2. 启动后台过期goroutine,负责从leveldb中删除过期的数据(stale data);

3. 启动loop,后台执行节点刷新、重验证等处理。下面写的步骤就在这个goroutine中;主要就是doRefresh:

4. 加载种子节点

5. 以自身为目标执行查找

6. 循环3遍:随机生成目标,执行查找。

###邻居节点发现流程 table.lookup

~~~

1. 在本地K桶中查找距离target最近的一批节点,最多bucketSize个(16个);记为result;(节点加入result的逻辑:从列表中查找节点i,使得d(i,target) > d(n,target);如果列表中还有空间,直接加入节点n;如果找到了有效的i,则用n替换i位置的节点)

2. 如果步骤1没有找到节点,则等待刷新完成(遗留:这里尚未看懂);

3. 从result中并发发起alpha个(3个)查询,向对方询问距离target最近的若干节点(udp.findnode);

4. 若查询失败,更新失败节点信息,若该节点总失败次数达到maxFindnodeFailures次(5次),则从本地移除该节点信息;

5. 若查询成功,对返回的节点执行bondall处理(__注意:这里会执行更新K桶的操作,不管pingpong是否成功,都会加入K桶。__如果某个节点总是连不上,会被刷新机制删掉的),去掉不在线节点;对在线节点建立连接;

6. 对在线节点,如果未见过,则按照步骤1的规则加入result中;

7. 循环从3开始的步骤,直到result中的所有节点都查询过了;

8. 返回result中的节点。

~~~

###节点连接及本地K桶维护流程 table.bond

在lookup中会对返回的节点执行bondall处理,bondall中主要是对每个节点执行bond处理。

bond确保本地节点和给定的远程节点有一个连接,如果连接成功,会放到本地一个连接table中。在执行findnode之前,必须有连接已建立。活跃的连接数有一定限制,以便限制网络负载占用。

不管pingpong是否成功,节点都会更新到本地K桶中。

~~~

1. 如果节点有一段时间没出现了或者对他执行findnode失败过,则执行pingpong;

2. 无论前述步骤是否执行或执行是否成功,都执行更新节点到K桶的处理。

~~~

__节点n更新到本地K桶:__

1. 获取n对应的K桶:设距离为d,计算log2(d)。实现上是获取d二进制表示时的最高位1所在的位置。若结果≤ bucketMinDistance(239),返回K[0],否则返回 K[结果-bucketMinDistance-1];

2. 如果n在K桶中已经存在,则将其移到最前面;否则如果K桶未满,则加进去;

3. 如果n没进入K桶中,则将其维护进候选列表中。

~~~

// add attempts to add the given node its corresponding bucket. If the

// bucket has space available, adding the node succeeds immediately.

// Otherwise, the node is added if the least recently active node in

// the bucket does not respond to a ping packet.

//

// The caller must not hold tab.mutex.

func (tab *Table) add(new *Node) {

    tab.mutex.Lock()

    defer tab.mutex.Unlock()

    b := tab.bucket(new.sha)

    if !tab.bumpOrAdd(b, new) {

        // Node is not in table. Add it to the replacement list.

        tab.addReplacement(b, new)

    }

}

转载    以太坊源码分析(14)P2P分析

猜你喜欢

转载自blog.csdn.net/zhichunqi/article/details/81812775